From 8e41b48e36c32bf43a08498516c12bcc87c86f21 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 15 Oct 2026 22:35:15 -0400 Subject: [PATCH] jcp-energy: the RAPL probe tests advance their fake zones on the probe's own tick Two tests checked the RAPL liveness probe on fake powercap trees: `the_rapl_probe_on_fake_powercap_trees` and `rapl_sums_and_probes_the_package_zones_only`. A second thread advanced the fake zones on a 2 ms timer while the probe's tick burned CPU on the calling thread. On a loaded CI host that thread was starved, the intervals went flat, and two live packages read Frozen. It failed PR #270's jcp-rs leg (job 29211: "both packages advancing, psys and dram holding, was not live"), and that PR does not touch jcp. `rapl_status_ticking` runs RAPL's probe with its real opener, sampler, CPU-burning tick, clock and budget, and adds the fake hardware's rise after each tick, on the probe's own thread. Every interval sees exactly the rise the test means, so the verdict comes from the probe's rule and not from the scheduler. `rapl_status_on`, the real composition, is still exercised by the same tests' Frozen and Unavailable cases. On the build host the two tests passed in 10 of 10 runs (Live in ~311 ms, Frozen at the 1 s budget). The full suite passes with `--features nvml`. Co-Authored-By: Claude Opus 5.5 --- jcp/jcp-rs/crates/jcp-energy/src/lib.rs | 103 +++++++++++++----------- 1 file changed, 55 insertions(+), 48 deletions(-) diff --git a/jcp/jcp-rs/crates/jcp-energy/src/lib.rs b/jcp/jcp-rs/crates/jcp-energy/src/lib.rs index 8c596f58c..a724bdf40 100644 --- a/jcp/jcp-rs/crates/jcp-energy/src/lib.rs +++ b/jcp/jcp-rs/crates/jcp-energy/src/lib.rs @@ -7368,39 +7368,53 @@ mod tests { } } + /// RAPL's probe — its opener on `tree`, its sampler, the CPU-burning tick, + /// the real clock, one budget — with `advance` run after each tick: the + /// fake hardware's rise for that interval. These tests once advanced the + /// zones from another thread on a 2 ms timer instead, which a loaded CI + /// host starved while the tick burned CPU: intervals went flat, and two + /// live packages read Frozen. On the probe's own thread the rise lands in + /// every interval, so the verdict is the rule's, not the scheduler's. + fn rapl_status_ticking(tree: &FakePowercap, mut advance: impl FnMut()) -> CounterStatus { + crate::liveness::status_of( + crate::liveness::probe( + || Rapl::open_in(&tree.root), + Rapl::liveness_sample, + || { + crate::liveness::burn_tick(); + advance(); + }, + std::time::Instant::now, + crate::liveness::BUDGET, + ) + .as_ref(), + ) + } + /// The real RAPL probe — the real reader on files, the CPU-burning tick, /// the real clock, one budget — on fake powercap trees. A tree whose - /// counters hold is Frozen; one whose two packages another thread - /// advances every 2 ms is Live; one where that thread advances package 0 - /// alone, package 1 holding, is Frozen; no tree, or a tree whose - /// `energy_uj` is not a number, is Unavailable, as RAPL was declined - /// before. + /// counters hold is Frozen; one whose two packages advance 4 mJ every tick + /// is Live; one where package 0 advances alone, package 1 holding, is + /// Frozen; no tree, or a tree whose `energy_uj` is not a number, is + /// Unavailable, as RAPL was declined before. #[test] fn the_rapl_probe_on_fake_powercap_trees() { - /// The real probe on `tree` while another thread advances the - /// packages in `advancing` by 4 mJ every 2 ms. + /// The probe on `tree`, the packages in `advancing` rising by 4 mJ at + /// every tick ([`rapl_status_ticking`]). fn probe_while_advancing(tree: &FakePowercap, advancing: &[usize]) -> CounterStatus { - let stop = std::sync::atomic::AtomicBool::new(false); - std::thread::scope(|s| { - s.spawn(|| { - let mut uj: u64 = 1_000; - while !stop.load(std::sync::atomic::Ordering::SeqCst) { - uj += 4_000; - for &k in advancing { - tree.write(k, &uj.to_string()); - } - std::thread::sleep(Duration::from_millis(2)); - } - }); - let started = std::time::Instant::now(); - let status = rapl_status_on(|| Rapl::open_in(&tree.root)); - eprintln!( - "rapl probe: a fake tree with packages {advancing:?} advancing every 2 ms -> {status:?} in {:?}", - started.elapsed() - ); - stop.store(true, std::sync::atomic::Ordering::SeqCst); - status - }) + let mut uj: u64 = 1_000; + let started = std::time::Instant::now(); + let status = rapl_status_ticking(tree, || { + uj += 4_000; + for &k in advancing { + tree.write(k, &uj.to_string()); + } + }); + eprintln!( + "rapl probe: a fake tree with packages {advancing:?} advancing every tick -> {status:?} in {:?}", + started.elapsed() + ); + status } #[cfg(target_os = "macos")] let _hw = ioreport_hw_lock(); @@ -8734,7 +8748,8 @@ mod tests { /// alone, although every other zone rose too; a package wrapping at its own /// range is corrected at that range. And the real probe is Live while the /// two packages advance with `psys` and `dram` holding, Frozen while - /// `psys`, `dram` and one package advance with the other package holding. + /// `psys`, `dram` and one package advance with the other package holding + /// (each advancing at every tick of the probe, [`rapl_status_ticking`]). #[test] fn rapl_sums_and_probes_the_package_zones_only() { #[cfg(target_os = "macos")] @@ -8778,26 +8793,18 @@ mod tests { assert_eq!((e.provenance, e.measured_uj), (Provenance::HwShunt, Some(12_487)), "{e:?}"); drop(meter); - /// The real probe on `tree` while another thread advances the zones - /// in `advancing` by 4 mJ every 2 ms. + /// The probe on `tree`, the zones in `advancing` rising by 4 mJ at + /// every tick ([`rapl_status_ticking`]). fn probe_advancing(tree: &FakePowercap, advancing: &[&str]) -> CounterStatus { - let stop = std::sync::atomic::AtomicBool::new(false); - std::thread::scope(|s| { - s.spawn(|| { - let mut uj: u64 = 100_000; - while !stop.load(std::sync::atomic::Ordering::SeqCst) { - uj += 4_000; - for dir in advancing { - tree.write_zone(dir, &uj.to_string()); - } - std::thread::sleep(Duration::from_millis(2)); - } - }); - let status = rapl_status_on(|| Rapl::open_in(&tree.root)); - eprintln!("rapl probe: zones {advancing:?} advancing, the rest holding -> {status:?}"); - stop.store(true, std::sync::atomic::Ordering::SeqCst); - status - }) + let mut uj: u64 = 100_000; + let status = rapl_status_ticking(tree, || { + uj += 4_000; + for dir in advancing { + tree.write_zone(dir, &uj.to_string()); + } + }); + eprintln!("rapl probe: zones {advancing:?} advancing, the rest holding -> {status:?}"); + status } let tree = two_sockets_with_psys_and_dram(); assert_eq!( -- 2.54.0 (Apple Git-157)