//! The tests' own reading of this process's CPU energy, shared by the test //! binaries of this directory that hold the cascade's figures against it. //! //! It shares no call and no number with the counter under test: //! `proc_pid_rusage` is declared here and read into raw words at the SDK's //! offset, and a read is taken only once this thread has left its core by a //! rule of the oracle's own — the process's context-switch count rose across //! a sleep — where the counter watches the reading thread's own energy count. //! That rule holds only while one thread of the process runs, so each binary //! here has one test. #![allow(dead_code)] use std::os::raw::{c_int, c_void}; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use eoc_meter::JouleCounter; unsafe extern "C" { fn proc_pid_rusage(pid: c_int, flavor: c_int, buffer: *mut c_void) -> c_int; fn proc_pidinfo(pid: c_int, flavor: c_int, arg: u64, buffer: *mut c_void, buffersize: c_int) -> c_int; fn clock_gettime_nsec_np(clock_id: u32) -> u64; } /// `CLOCK_THREAD_CPUTIME_ID` in macOS ``. const CLOCK_THREAD_CPUTIME_ID: u32 = 16; /// This thread's CPU time. fn thread_cpu() -> Duration { Duration::from_nanos(unsafe { clock_gettime_nsec_np(CLOCK_THREAD_CPUTIME_ID) }) } /// Spin this thread until it has used `d` of its own CPU time (or 20 s of /// wall time have passed), however loaded the host is. pub fn burn(d: Duration) { let (started, wall) = (thread_cpu(), Instant::now()); let mut acc: u64 = 0; while thread_cpu() - started < d && wall.elapsed() < Duration::from_secs(20) { for _ in 0..1_000 { acc = std::hint::black_box(acc.wrapping_add(1)); } } } fn me() -> c_int { std::process::id() as c_int } /// This process's `ri_energy_nj` as it stands: `proc_pid_rusage(pid, /// RUSAGE_INFO_V6)` into 464 raw bytes, the count at byte 336. fn energy_nj() -> u64 { let mut words = [0u64; 464 / 8]; let rc = unsafe { proc_pid_rusage(me(), 6, words.as_mut_ptr() as *mut c_void) }; assert_eq!(rc, 0, "the oracle's proc_pid_rusage"); words[336 / 8] } /// How many times this process's threads have been switched off a core: /// `pti_csw`, the `int32_t` at byte 80 of the 96-byte `proc_taskinfo` that /// `proc_pidinfo(pid, PROC_PIDTASKINFO = 4)` fills. fn context_switches() -> i32 { let mut info = [0i32; 96 / 4]; let wrote = unsafe { proc_pidinfo(me(), 4, 0, info.as_mut_ptr() as *mut c_void, 96) }; assert_eq!(wrote, 96, "the oracle's proc_pidinfo(PROC_PIDTASKINFO)"); info[80 / 4] } /// The oracle's read of this process's energy, in nanojoules, taken once /// this thread has left its core: a sleep of 200 µs, then 1, 2, 4 and 8 ms, /// until the process's context-switch count rose across one. Only the test's /// thread runs in the process, so a rise is this thread's switch, and the /// read after it holds all the thread spent before it. pub fn energy_nj_off_core() -> u64 { let left = [200, 1_000, 2_000, 4_000, 8_000].into_iter().any(|micros| { let before = context_switches(); std::thread::sleep(Duration::from_micros(micros)); context_switches() > before }); assert!(left, "the oracle never saw this thread leave its core across sleeps of 0.2, 1, 2, 4 and 8 ms"); energy_nj() } /// One read of a [`Watched`] counter: the oracle's reading before it, what /// the counter read (`None` for a failed read), the oracle's after it, and /// where the test's own count stood when the read was asked for. #[derive(Debug, Clone, Copy)] pub struct Read { pub before_nanojoules: u64, pub microjoules: Option, pub after_nanojoules: u64, pub count: u64, } /// A counter with the oracle's own read on either side of every read of it, /// kept in order. A span between two of its reads therefore lies between two /// windows of the oracle's: the one inside the span, from the oracle's read /// after the opening read to its read before the closing one, and the one /// around the span and both reads. /// /// The oracle's reads between the two readings of a span are the span's too: /// its sleep after the opening read and before the closing one. So a span /// through this counter reads a little more than one through the bare /// counter would, and both windows hold it. pub struct Watched { counter: C, reads: Mutex>, count: Arc, } impl Watched { pub fn new(counter: C) -> Self { Self::beside(counter, Arc::new(AtomicU64::new(0))) } /// A watched counter that also notes, at each read, where `count` /// stood: a count the test moves, to place a read among its own steps. pub fn beside(counter: C, count: Arc) -> Self { Self { counter, reads: Mutex::new(Vec::new()), count } } /// The reads taken since the last call, in order. pub fn take(&self) -> Vec { std::mem::take(&mut *self.reads.lock().unwrap()) } } impl JouleCounter for Watched { fn read_microjoules(&self) -> eoc_core::Result { let count = self.count.load(Ordering::SeqCst); let before_nanojoules = energy_nj_off_core(); let read = self.counter.read_microjoules(); let after_nanojoules = energy_nj_off_core(); self.reads.lock().unwrap().push(Read { before_nanojoules, microjoules: read.as_ref().ok().copied(), after_nanojoules, count, }); read } fn name(&self) -> &'static str { self.counter.name() } } /// A span between two reads of a [`Watched`] counter, in nanojoules: what /// the counter read, and the oracle's two windows. #[derive(Debug, Clone, Copy)] pub struct Span { /// The counter's two reads' delta; `None` when either failed or the /// counter did not advance. pub read: Option, /// The oracle's window inside the span. pub inside: u64, /// The oracle's window around the span and both of its reads. pub around: u64, } impl Span { pub fn between(open: &Read, close: &Read) -> Self { let read = match (open.microjoules, close.microjoules) { (Some(open), Some(close)) if close > open => Some((close - open) * 1_000), _ => None, }; Self { read, inside: close.before_nanojoules - open.after_nanojoules, around: close.after_nanojoules - open.before_nanojoules, } } /// Whether `nanojoules`, a figure for this span, lies between the /// oracle's two windows, give or take the micro-joule the counter's two /// reads round away. The count only grows, so a reading of the span that /// does not is not this process's count. pub fn holds(&self, nanojoules: u64) -> bool { self.inside < nanojoules + 1_000 && nanojoules < self.around + 1_000 } /// The window inside the span as thousandths of the one around it: how /// closely the oracle pins the span. pub fn tightness(&self) -> u64 { self.inside * 1_000 / self.around.max(1) } } /// A counter that counts its reads. pub struct Counted { counter: C, reads: Mutex, } impl Counted { pub fn new(counter: C) -> Self { Self { counter, reads: Mutex::new(0) } } pub fn reads(&self) -> u64 { *self.reads.lock().unwrap() } } impl JouleCounter for Counted { fn read_microjoules(&self) -> eoc_core::Result { *self.reads.lock().unwrap() += 1; self.counter.read_microjoules() } fn name(&self) -> &'static str { self.counter.name() } }