#![forbid(unsafe_code)] use std::time::Instant; /// Sample per-process CPU pressure from Linux procfs. /// /// Inputs: none when constructed; call `sample_percent()` over time. /// Outputs: percentage of one CPU core used by the current process over the /// elapsed sampling window. /// Why: this gives the launcher and server lightweight pressure telemetry /// without adding a heavyweight system-information dependency. #[derive(Debug, Clone, Default)] pub struct ProcessCpuSampler { last: Option<(Instant, u64)>, } impl ProcessCpuSampler { #[must_use] pub fn new() -> Self { Self::default() } pub fn sample_percent(&mut self) -> Option { let now = Instant::now(); let runtime_ns = read_process_runtime_ns()?; let previous = self.last.replace((now, runtime_ns))?; let elapsed_ns = now .saturating_duration_since(previous.0) .as_nanos() .min(u128::from(u64::MAX)) as u64; if elapsed_ns == 0 || runtime_ns < previous.1 { return None; } Some(runtime_ns.saturating_sub(previous.1) as f32 * 100.0 / elapsed_ns as f32) } pub fn sample_tenths_percent(&mut self) -> Option { self.sample_percent() .map(|pct| (pct * 10.0).clamp(0.0, u32::MAX as f32) as u32) } } fn read_process_runtime_ns() -> Option { let text = std::fs::read_to_string("/proc/self/schedstat").ok()?; text.split_whitespace().next()?.parse::().ok() } #[cfg(test)] mod tests { use super::*; use std::thread; use std::time::Duration; #[test] fn schedstat_runtime_reads() { assert!(read_process_runtime_ns().is_some()); } #[test] fn sampler_returns_percentage_after_two_samples() { let mut sampler = ProcessCpuSampler::new(); assert!(sampler.sample_percent().is_none()); thread::sleep(Duration::from_millis(10)); let _ = sampler.sample_percent(); } }