//! The liveness probes behind `ioreport_status()` and //! `process_energy_status()` each run once per PROCESS, however many threads //! ask at once, through the statuses, the selection and the metering entry //! point: a probe per call or per thread would burn up to ~1 s on each //! caller's thread (inside a measurement, a whole probe in its window) and //! could flip the verdict mid-process. (`power_over` is private; the lib test //! `the_gated_entry_points_read_the_cached_status` pins it the same way, on //! the calling thread's CPU.) //! //! This file holds this one test, so that it runs in a process of its own and //! IS the first use of the meter in that process. The counters it reads, //! `real_probe_runs()` and `real_process_probe_runs()`, count runs of the //! production probes (`host_status`, `process_host_status`, whose first //! statements they are), not lookups of the caches in front of them. use proof_cascade::meter::{ hardware_meter_available, ioreport_status, measure_marginal_uj, meter_source, process_energy_status, real_probe_runs, real_process_probe_runs, CounterStatus, IoReportStatus, MeterSource, }; use std::sync::{Arc, Barrier}; use std::time::Duration; /// Real, cheap, pure CPU work. fn cpu_work() { std::hint::black_box((0..4000u64).fold(0u64, |a, b| a.wrapping_mul(31).wrapping_add(b))); } /// This thread's CPU time, read through this test's own binding of /// `clock_gettime(CLOCK_THREAD_CPUTIME_ID)` (16 on macOS). #[cfg(target_os = "macos")] fn thread_cpu() -> Duration { #[repr(C)] struct Timespec { tv_sec: i64, tv_nsec: std::os::raw::c_long, } unsafe extern "C" { fn clock_gettime(clock_id: std::os::raw::c_int, tp: *mut Timespec) -> std::os::raw::c_int; } let mut ts = Timespec { tv_sec: 0, tv_nsec: 0 }; // SAFETY: ts is a valid, writable timespec. assert_eq!(unsafe { clock_gettime(16, &mut ts) }, 0, "the thread CPU clock does not read"); Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32) } #[cfg(not(target_os = "macos"))] fn thread_cpu() -> Duration { Duration::ZERO } /// Eight threads, released together, each ask for both statuses and then /// select the meter the way a caller does (`meter_source()` and /// `hardware_meter_available()` are the selection; `measure_marginal_uj` /// selects and, declined, gives no figure). Each probe must have run exactly /// once for all of them. Once a thread has its statuses, its selection calls /// must be warm: together they may not burn 10 ms of that thread's CPU, where /// re-running a probe by any route costs at least five 25 ms ticks, and /// normally 0.3–1 s of burn. #[test] fn the_liveness_probe_runs_once_per_process() { assert_eq!(real_probe_runs(), 0, "the meter was used in this process before this test: this is not its first use"); assert_eq!(real_process_probe_runs(), 0, "the process counter was used in this process before this test"); const THREADS: usize = 8; let gate = Arc::new(Barrier::new(THREADS)); let handles: Vec<_> = (0..THREADS) .map(|_| { let gate = gate.clone(); std::thread::spawn(move || { gate.wait(); let status = ioreport_status(); let process = process_energy_status(); let c0 = thread_cpu(); let source = meter_source(); let selected = hardware_meter_available(); let again = (ioreport_status(), process_energy_status()); let figure = if selected { None } else { measure_marginal_uj(cpu_work) }; let warm = thread_cpu() - c0; (status, process, source, again, selected, figure, warm) }) }) .collect(); let seen: Vec<_> = handles.into_iter().map(|h| h.join().expect("a probing thread panicked")).collect(); let (runs, process_runs) = (real_probe_runs(), real_process_probe_runs()); let warmest = seen.iter().map(|s| s.6).max().unwrap_or_default(); let (status, process, source) = (seen[0].0, seen[0].1, seen[0].2); eprintln!( "ioreport cache: {THREADS} threads saw (ioreport={status:?}, process={process:?}, source={source:?}); real \ probe runs in this process: ioreport {runs}, process {process_runs}; warm selection calls burned at most \ {warmest:?} of a thread's CPU" ); for (i, (s, p, src, again, selected, figure, _)) in seen.iter().enumerate() { assert_eq!((*s, *p, *again), (status, process, (status, process)), "thread {i} was given a different status"); assert_eq!((*src, *selected), (source, source.is_some()), "thread {i} made a different selection"); if !selected { assert_eq!(*figure, None, "thread {i}: a declined meter gave a figure"); } } if cfg!(target_os = "macos") { let want = if process == CounterStatus::Live { Some(MeterSource::ProcessEnergy) } else if status == IoReportStatus::Live { Some(MeterSource::IoReport) } else { None }; assert_eq!(source, want, "on macOS: the process counter if Live, else IOReport if Live, else none"); assert_eq!(runs, 1, "the IOReport probe ran {runs} times in this process ({THREADS} threads asked at once)"); assert_eq!(process_runs, 1, "the process-counter probe ran {process_runs} times ({THREADS} threads asked at once)"); for (i, s) in seen.iter().enumerate() { assert!( s.6 < Duration::from_millis(10), "thread {i}: its warm selection calls burned {:?} of its CPU: something probed again, uncached", s.6 ); } } else { assert_eq!(status, IoReportStatus::Unavailable, "IOReport off macOS"); assert_eq!(process, CounterStatus::Unavailable, "a process counter off macOS"); assert_eq!((runs, process_runs), (0, 0), "a liveness probe ran off macOS"); } }