#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ProcessResourceUsage {
pub cpu_us: i64,
pub rss_bytes: i64,
}
#[cfg(unix)]
pub fn process_resource_usage() -> Option<ProcessResourceUsage> {
let usage: libc::rusage = unsafe {
let mut usage: libc::rusage = std::mem::zeroed();
if libc::getrusage(libc::RUSAGE_SELF, &mut usage) != 0 {
return None;
}
usage
};
let user_us = usage.ru_utime.tv_sec * 1_000_000 + i64::from(usage.ru_utime.tv_usec as i32);
let sys_us = usage.ru_stime.tv_sec * 1_000_000 + i64::from(usage.ru_stime.tv_usec as i32);
let rss_bytes: i64 = if cfg!(target_os = "macos") {
usage.ru_maxrss as i64
} else {
usage.ru_maxrss as i64 * 1024
};
Some(ProcessResourceUsage {
cpu_us: user_us + sys_us,
rss_bytes,
})
}
#[cfg(not(unix))]
pub fn process_resource_usage() -> Option<ProcessResourceUsage> {
None
}
pub fn cpu_delta_us(
start: Option<ProcessResourceUsage>,
end: Option<ProcessResourceUsage>,
) -> Option<i64> {
match (start, end) {
(Some(start), Some(end)) => Some(end.cpu_us.saturating_sub(start.cpu_us).max(0)),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn process_resource_usage_returns_positive_values() {
let mut acc: u64 = 0;
for i in 0..1_000_000u64 {
acc = acc.wrapping_add(i);
}
std::hint::black_box(acc);
let usage = process_resource_usage().expect("getrusage must succeed on unix CI runners");
assert!(usage.cpu_us >= 0, "cpu_us must be non-negative");
assert!(
usage.rss_bytes > 0,
"rss_bytes must be positive for a live process"
);
}
#[test]
fn cpu_delta_us_reports_the_difference_not_the_cumulative_end_value() {
let start = ProcessResourceUsage {
cpu_us: 500 * 60 * 1_000_000, rss_bytes: 1_000_000,
};
let end = ProcessResourceUsage {
cpu_us: 500 * 60 * 1_000_000 + 1_500, rss_bytes: 1_100_000,
};
let delta = cpu_delta_us(Some(start), Some(end)).expect("both snapshots present");
assert_eq!(
delta, 1_500,
"delta must be end-minus-start, not the cumulative end value"
);
assert!(
delta < start.cpu_us,
"a correct delta must be far smaller than the pre-existing cumulative total"
);
}
#[test]
fn cpu_delta_us_saturates_instead_of_underflowing_on_a_spurious_decrease() {
let start = ProcessResourceUsage {
cpu_us: 1_000,
rss_bytes: 0,
};
let end = ProcessResourceUsage {
cpu_us: 500,
rss_bytes: 0,
};
assert_eq!(cpu_delta_us(Some(start), Some(end)), Some(0));
}
#[test]
fn cpu_delta_us_is_none_when_either_snapshot_is_unavailable() {
let snap = ProcessResourceUsage {
cpu_us: 10,
rss_bytes: 0,
};
assert_eq!(cpu_delta_us(None, Some(snap)), None);
assert_eq!(cpu_delta_us(Some(snap), None), None);
assert_eq!(cpu_delta_us(None, None), None);
}
}