Skip to main content

vtcode_commons/
memory.rs

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