Skip to main content

resource_sampler/
cpu.rs

1use std::thread;
2use std::time::Duration;
3
4/// Sample CPU usage over ~200ms and return a percentage (0–100*ncpus).
5///
6/// This mirrors the behaviour previously implemented in
7/// `peek-core::proc::resources::sample_cpu`.
8pub fn sample_cpu(pid: i32) -> Option<f64> {
9    let (pid_t1, sys_t1) = read_cpu_ticks(pid)?;
10    thread::sleep(Duration::from_millis(200));
11    let (pid_t2, sys_t2) = read_cpu_ticks(pid)?;
12
13    let d_pid = pid_t2.saturating_sub(pid_t1) as f64;
14    let d_sys = sys_t2.saturating_sub(sys_t1) as f64;
15
16    if d_sys == 0.0 {
17        return None;
18    }
19    // Number of logical CPUs from /proc/stat non-aggregate lines
20    let ncpus = cpu_count().max(1) as f64;
21    Some((d_pid / d_sys) * 100.0 * ncpus)
22}
23
24fn read_cpu_ticks(pid: i32) -> Option<(u64, u64)> {
25    // Process ticks from /proc/<pid>/stat fields 14 (utime) + 15 (stime)
26    let stat_raw = std::fs::read_to_string(format!("/proc/{}/stat", pid)).ok()?;
27    // Field 14 and 15 are utime and stime (0-indexed: 13 and 14)
28    // The comm field may contain spaces, find the closing ')'
29    let after_comm = stat_raw.rfind(')').map(|i| &stat_raw[i + 2..])?;
30    let fields: Vec<&str> = after_comm.split_whitespace().collect();
31    // After ')' and state char: field index 11=utime, 12=stime (0-indexed)
32    let utime: u64 = fields.get(11)?.parse().ok()?;
33    let stime: u64 = fields.get(12)?.parse().ok()?;
34    let process_ticks = utime + stime;
35
36    // Total system ticks from /proc/stat first line
37    let kstat_raw = std::fs::read_to_string("/proc/stat").ok()?;
38    let first_line = kstat_raw.lines().next()?;
39    let cpu_fields: Vec<u64> = first_line
40        .split_whitespace()
41        .skip(1)
42        .filter_map(|s| s.parse().ok())
43        .collect();
44    let total_ticks: u64 = cpu_fields.iter().sum();
45
46    Some((process_ticks, total_ticks))
47}
48
49fn cpu_count() -> usize {
50    std::fs::read_to_string("/proc/stat")
51        .map(|s| {
52            s.lines()
53                .filter(|l| l.starts_with("cpu") && l.len() > 3)
54                .count()
55        })
56        .unwrap_or(1)
57}