vtcode_commons/memory.rs
1#![cfg_attr(
2 any(target_os = "macos", target_os = "linux"),
3 expect(
4 clippy::cast_possible_truncation,
5 reason = "RSS sampling casts platform ABI integer sizes to f64 for MB-scale reporting; truncation is acceptable."
6 )
7)]
8
9//! Resident Set Size (RSS) sampling for memory diagnostics.
10//!
11//! Used by the allocator benchmark (`vtcode bench-allocator`) to measure whether
12//! the global allocator returns memory to the OS after bursty/sparse workloads.
13//! Unlike `performance_profiler::get_memory_usage_mb` (Linux `/proc` only, fake
14//! fallback on macOS), this returns a real value on every supported platform.
15use std::time::Duration;
16
17/// Returns the current process Resident Set Size in **megabytes**, or `None` if
18/// it cannot be determined on the current platform.
19#[cfg(target_os = "macos")]
20#[allow(
21 deprecated,
22 unsafe_code,
23 unused_qualifications,
24 reason = "Intentional compatibility, platform, or test-only suppression."
25)] // libc::mach_task_self is deprecated; qualification is required here
26fn resident_set_size_mb() -> Option<f64> {
27 // SAFETY: `mach_task_basic_info` is a plain old data struct; zeroing it
28 // produces a valid (all-zero) starting value before `task_info` fills it.
29 let mut info: libc::mach_task_basic_info = unsafe { std::mem::zeroed() };
30 let mut count = (std::mem::size_of::<libc::mach_task_basic_info>() / std::mem::size_of::<libc::integer_t>())
31 as libc::mach_msg_type_number_t;
32 // SAFETY: `mach_task_self()` returns a send-right to the current task with
33 // no preconditions; it cannot fail to produce a valid port name.
34 let task = unsafe { libc::mach_task_self() };
35 // SAFETY: `task` is our own task port; `info` and `count` are valid
36 // out-pointers of the expected size, and `task_info` only writes them on
37 // success.
38 let ret = unsafe {
39 libc::task_info(task, libc::MACH_TASK_BASIC_INFO, &mut info as *mut _ as *mut libc::integer_t, &mut count)
40 };
41 if ret != libc::KERN_SUCCESS {
42 return None;
43 }
44 Some(info.resident_size as f64 / (1024.0 * 1024.0))
45}
46
47/// Returns the current process Resident Set Size in **megabytes**, or `None` if
48/// it cannot be determined on the current platform.
49#[cfg(target_os = "linux")]
50#[allow(
51 unsafe_code,
52 reason = "Intentional compatibility, platform, or test-only suppression."
53)]
54pub fn resident_set_size_mb() -> Option<f64> {
55 let contents = std::fs::read_to_string("/proc/self/statm").ok()?;
56 let field = contents.split_whitespace().nth(1)?;
57 let pages: f64 = field.parse().ok()?;
58 // SAFETY: `_SC_PAGESIZE` is a compile-time constant selector passed by value.
59 // `sysconf` only reads the selector and returns a `c_long`; it performs no
60 // mutable aliasing against process memory and has no preconditions on this
61 // input. The result is a stable system constant for the process lifetime.
62 let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as f64;
63 Some(pages * page_size / (1024.0 * 1024.0))
64}
65
66/// Fallback for unsupported platforms.
67#[cfg(not(any(target_os = "macos", target_os = "linux")))]
68pub fn resident_set_size_mb() -> Option<f64> {
69 None
70}
71
72/// Sample RSS once and return the value in MB (0.0 if unavailable).
73pub fn sample_rss_mb() -> f64 {
74 resident_set_size_mb().unwrap_or(0.0)
75}
76
77/// Sample RSS repeatedly, returning the maximum observed value in MB.
78/// Useful for capturing peak memory during a burst of activity.
79pub fn sample_peak_rss_mb(duration: Duration, poll_interval: Duration) -> f64 {
80 let start = std::time::Instant::now();
81 let mut peak = 0.0;
82 while start.elapsed() < duration {
83 let v = sample_rss_mb();
84 if v > peak {
85 peak = v;
86 }
87 std::thread::sleep(poll_interval);
88 }
89 peak
90}