Skip to main content

scx_pandemonium/cli/
probe.rs

1use std::sync::atomic::{AtomicBool, Ordering};
2
3static RUNNING: AtomicBool = AtomicBool::new(true);
4
5// PRE-ALLOCATED SAMPLE BUFFER -- NO I/O DURING MEASUREMENT
6const MAX_SAMPLES: usize = 16384;
7
8/// Interactive wakeup probe.
9/// When PANDEMONIUM is running, BPF records latencies to ring buffer.
10/// For EEVDF baseline, we measure in userspace.
11/// Either way: ZERO I/O during measurement, bulk output at end.
12pub fn run_probe() {
13    ctrlc::set_handler(move || {
14        RUNNING.store(false, Ordering::Relaxed);
15    })
16    .ok();
17
18    let mut samples: Vec<i64> = Vec::with_capacity(MAX_SAMPLES);
19
20    let period_ns: i64 = 10_000_000; // 10MS PROBE PERIOD
21
22    // COORDINATED-OMISSION-CORRECT: sleep to an ABSOLUTE running deadline
23    // (CLOCK_MONOTONIC, TIMER_ABSTIME), not a relative nanosleep. When a
24    // scheduler stall makes us oversleep past later deadlines, backfill one
25    // sample per swallowed deadline (HdrHistogram recordValueWithExpectedInterval
26    // semantics). A relative nanosleep records a single long overshoot and
27    // drops the queue of deadlines the stall ate -- understating the tail.
28    let mut now = libc::timespec {
29        tv_sec: 0,
30        tv_nsec: 0,
31    };
32    unsafe {
33        libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut now);
34    }
35    let mut deadline_ns: i64 = now.tv_sec * 1_000_000_000 + now.tv_nsec + period_ns;
36
37    while RUNNING.load(Ordering::Relaxed) && samples.len() < MAX_SAMPLES {
38        let dl = libc::timespec {
39            tv_sec: deadline_ns / 1_000_000_000,
40            tv_nsec: deadline_ns % 1_000_000_000,
41        };
42        unsafe {
43            libc::clock_nanosleep(
44                libc::CLOCK_MONOTONIC,
45                libc::TIMER_ABSTIME,
46                &dl,
47                std::ptr::null_mut(),
48            );
49            libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut now);
50        }
51        let now_ns = now.tv_sec * 1_000_000_000 + now.tv_nsec;
52
53        // SAMPLE THIS DEADLINE, THEN BACKFILL ANY DEADLINES THE STALL SWALLOWED.
54        loop {
55            let lateness_us = (now_ns - deadline_ns).max(0) / 1000;
56            samples.push(lateness_us);
57            deadline_ns += period_ns;
58            if now_ns < deadline_ns || samples.len() >= MAX_SAMPLES {
59                break;
60            }
61        }
62    }
63
64    // BULK OUTPUT AT END -- USE write() DIRECTLY TO MINIMIZE OVERHEAD
65    use std::io::Write;
66    let stdout = std::io::stdout();
67    let mut handle = stdout.lock();
68    for s in &samples {
69        let _ = writeln!(handle, "{}", s);
70    }
71}