use sysinfo::{Pid, ProcessRefreshKind, ProcessesToUpdate, RefreshKind, System};
#[cfg(target_os = "macos")]
pub fn physical_footprint_mb(pid: u32) -> Option<u64> {
let mut info: libc::rusage_info_v0 = unsafe { std::mem::zeroed() };
let ret = unsafe {
libc::proc_pid_rusage(
pid as libc::c_int,
libc::RUSAGE_INFO_V0,
std::ptr::addr_of_mut!(info).cast(),
)
};
if ret != 0 {
return None;
}
Some(info.ri_phys_footprint / (1024 * 1024))
}
pub struct SysMetrics {
sys: System,
pid: Pid,
}
impl SysMetrics {
#[must_use]
pub fn new() -> Self {
let pid = Pid::from_u32(std::process::id());
let mut sys = System::new_with_specifics(
RefreshKind::nothing()
.with_processes(ProcessRefreshKind::nothing().with_memory().with_cpu()),
);
sys.refresh_processes_specifics(
ProcessesToUpdate::Some(&[pid]),
true,
ProcessRefreshKind::nothing().with_memory().with_cpu(),
);
Self { sys, pid }
}
pub fn sample(&mut self) -> (u64, f32) {
self.sys.refresh_processes_specifics(
ProcessesToUpdate::Some(&[self.pid]),
true,
ProcessRefreshKind::nothing().with_memory().with_cpu(),
);
let Some(proc) = self.sys.process(self.pid) else {
return (0, 0.0);
};
let sysinfo_rss_mb = proc.memory() / (1024 * 1024);
let cpu_pct = proc.cpu_usage();
#[cfg(target_os = "macos")]
let rss_mb = physical_footprint_mb(self.pid.as_u32()).unwrap_or(sysinfo_rss_mb);
#[cfg(not(target_os = "macos"))]
let rss_mb = sysinfo_rss_mb;
(rss_mb, cpu_pct)
}
}
impl Default for SysMetrics {
fn default() -> Self {
Self::new()
}
}
#[must_use]
pub fn dir_size_bytes(dir: &std::path::Path) -> u64 {
fn walk(dir: &std::path::Path, total: &mut u64) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let Ok(file_type) = entry.file_type() else {
continue;
};
if file_type.is_symlink() {
continue;
}
if file_type.is_dir() {
walk(&entry.path(), total);
continue;
}
if !file_type.is_file() {
continue;
}
if let Ok(meta) = entry.metadata() {
*total = total.saturating_add(meta.len());
}
}
}
let mut total = 0u64;
walk(dir, &mut total);
total
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sample_does_not_panic() {
let mut m = SysMetrics::new();
let (_rss, _cpu) = m.sample();
let (_rss2, cpu2) = m.sample();
assert!(cpu2 >= 0.0, "cpu usage must be non-negative, got {cpu2}");
}
#[test]
fn rss_is_plausible() {
let mut m = SysMetrics::new();
let (rss, _cpu) = m.sample();
assert!(
rss < 1024 * 1024,
"RSS implausibly large ({rss} MB) — unit must be MB"
);
}
#[test]
fn dir_size_sums_files() {
let tmp = tempfile::tempdir().expect("tempdir");
std::fs::write(tmp.path().join("a.txt"), vec![0u8; 100]).unwrap();
std::fs::write(tmp.path().join("b.txt"), vec![0u8; 250]).unwrap();
let sub = tmp.path().join("sub");
std::fs::create_dir(&sub).unwrap();
std::fs::write(sub.join("c.txt"), vec![0u8; 50]).unwrap();
assert_eq!(dir_size_bytes(tmp.path()), 400);
}
#[test]
fn dir_size_missing_dir_is_zero() {
let missing = std::path::Path::new("/nonexistent/trusty/path/xyz");
assert_eq!(dir_size_bytes(missing), 0);
}
#[cfg(target_os = "macos")]
#[test]
fn self_physical_footprint_is_plausible() {
let pid = std::process::id();
let mb = physical_footprint_mb(pid).expect("proc_pid_rusage must resolve our own pid");
assert!(mb > 0, "physical footprint should be > 0 MB, got {mb}");
assert!(
mb < 1024 * 1024,
"physical footprint implausibly large ({mb} MB) — unit must be MB"
);
}
#[cfg(target_os = "macos")]
#[test]
fn physical_footprint_bogus_pid_returns_none() {
assert_eq!(physical_footprint_mb(u32::MAX), None);
}
#[cfg(target_os = "macos")]
#[test]
fn physical_footprint_tracks_real_allocation_growth() {
let pid = std::process::id();
let before = physical_footprint_mb(pid).expect("must resolve our own pid");
let mut touched: Vec<u8> = vec![0u8; 200 * 1024 * 1024];
for byte in touched.iter_mut().step_by(4096) {
*byte = 1;
}
let after = physical_footprint_mb(pid).expect("must resolve our own pid");
assert!(
after >= before + 100,
"expected footprint to grow by >= 100 MB after touching a 200 MB \
allocation; before={before} after={after}"
);
drop(touched);
}
}