diff --git a/jcp/jcp-rs/crates/jcp-energy/src/lib.rs b/jcp/jcp-rs/crates/jcp-energy/src/lib.rs index e72664012..ed961003d 100644 --- a/jcp/jcp-rs/crates/jcp-energy/src/lib.rs +++ b/jcp/jcp-rs/crates/jcp-energy/src/lib.rs @@ -5774,6 +5774,9 @@ mod tests { static N: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); let n = N.fetch_add(1, std::sync::atomic::Ordering::SeqCst); let root = std::env::temp_dir().join(format!("jcp-energy-powercap-{}-{n}", std::process::id())); + // A test process that was killed never dropped its trees, and a + // later one with the same pid would build on top of them. + let _ = std::fs::remove_dir_all(&root); let fake = FakePowercap { root }; std::fs::create_dir_all(&fake.root).expect("create a fake powercap tree"); for (dir, name, value, range) in zones { diff --git a/jouleclaw/jouleclaw-rs/crates/jouleclaw-energy/tests/process_cpu.rs b/jouleclaw/jouleclaw-rs/crates/jouleclaw-energy/tests/process_cpu.rs index 8f1995842..cfaf9cbb8 100644 --- a/jouleclaw/jouleclaw-rs/crates/jouleclaw-energy/tests/process_cpu.rs +++ b/jouleclaw/jouleclaw-rs/crates/jouleclaw-energy/tests/process_cpu.rs @@ -71,13 +71,17 @@ fn the_process_counter_meters_this_process_and_nothing_else() { assert_eq!((r.domain, r.provenance), (EnergyDomain::ProcessCpu, Provenance::HwShunt)); assert_eq!((counter.domain(), counter.provenance(), counter.name()), (EnergyDomain::ProcessCpu, Provenance::HwShunt, "process-cpu")); - // 2. 200 ms of one thread's CPU time reads 0.2 to 20 J. + // 2. 200 ms of one thread's CPU time reads 0.1 to 20 J. The floor is + // 0.1 J, not 0.2, because what 200 ms of CPU costs depends on the core + // class that ran it: 0.18-1.24 J on an M5 Max, and 0.171 J on a busy + // host (the oracle agreeing). The sleep below must read under 0.1 J, + // so a counter that reads wall time at a fixed power still fails one. let t = Instant::now(); let ((), burned) = counter.measure(|| burn(Duration::from_millis(200))); let wall = t.elapsed(); let uj = burned.expect("a 200 ms burn is a reading"); println!("process-cpu: 200 ms one-thread CPU burn = {:.3} J over {:.0} ms wall ({:.2} J per CPU-second)", uj as f64 / 1e6, wall.as_secs_f64() * 1e3, uj as f64 / 1e6 / 0.2); - assert!((200_000..=20_000_000).contains(&uj), "{uj} µJ for 200 ms of one thread's CPU"); + assert!((100_000..=20_000_000).contains(&uj), "{uj} µJ for 200 ms of one thread's CPU"); // 3. A 200 ms sleep reads under 0.1 J — or, if nothing of this process ran, // is not a reading at all; never a zero. diff --git a/sandbox/sandbox-rs/crates/sandbox-meter/src/probe/rapl.rs b/sandbox/sandbox-rs/crates/sandbox-meter/src/probe/rapl.rs index e41d77a05..e15a0dfaa 100644 --- a/sandbox/sandbox-rs/crates/sandbox-meter/src/probe/rapl.rs +++ b/sandbox/sandbox-rs/crates/sandbox-meter/src/probe/rapl.rs @@ -423,6 +423,9 @@ mod tests { static N: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); let n = N.fetch_add(1, std::sync::atomic::Ordering::SeqCst); let root = std::env::temp_dir().join(format!("sandbox-meter-powercap-{}-{n}", std::process::id())); + // A test process that was killed never dropped its trees, and a + // later one with the same pid would build on top of them. + let _ = fs::remove_dir_all(&root); let fake = FakePowercap { root }; fs::create_dir_all(&fake.root).expect("create a fake powercap tree"); for (dir, name, value, range) in zones { @@ -544,27 +547,24 @@ mod tests { /// holds; and a tree with no package zone is never ticked. #[test] fn the_liveness_run_watches_the_package_zones_only() { - /// The run over `tree` while another thread advances the zones in - /// `advancing` by 4 mJ every 2 ms. + /// The run over `tree` with each tick advancing the zones in + /// `advancing` by 4 mJ, then sleeping 25 ms: every interval sees one + /// advance of those zones and none of the rest, however loaded the + /// host is. (A writer thread on a 2 ms timer could be starved for a + /// whole interval on a busy host, and the run then read a live tree + /// as Frozen.) fn run_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(dir, &uj.to_string()); - } - std::thread::sleep(Duration::from_millis(2)); - } - }); - let watch = run_over(&tree.root, || std::thread::sleep(Duration::from_millis(25))); - let status = counter_status_of(watch.verdict()); - eprintln!("rapl run: zones {advancing:?} advancing, the rest holding -> {status:?}"); - stop.store(true, std::sync::atomic::Ordering::SeqCst); - status - }) + let mut uj: u64 = 100_000; + let watch = run_over(&tree.root, || { + uj += 4_000; + for dir in advancing { + tree.write(dir, &uj.to_string()); + } + std::thread::sleep(Duration::from_millis(25)); + }); + let status = counter_status_of(watch.verdict()); + eprintln!("rapl run: zones {advancing:?} advancing, the rest holding -> {status:?}"); + status } let tree = two_sockets_with_psys_and_dram(); assert_eq!( @@ -675,24 +675,17 @@ mod tests { /// broken count stays broken. #[test] fn the_public_package_counter_opens_only_over_live_packages() { - /// `RaplPackages::live_in(tree)` while another thread advances the - /// zones in `advancing` by 4 mJ every 2 ms. + /// `RaplPackages::live_in(tree)` with each tick advancing the zones + /// in `advancing` by 4 mJ, then sleeping 25 ms, as in + /// `the_liveness_run_watches_the_package_zones_only`. fn live_in_advancing(tree: &FakePowercap, advancing: &[&str]) -> Result { - 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(dir, &uj.to_string()); - } - std::thread::sleep(Duration::from_millis(2)); - } - }); - let opened = RaplPackages::live_in(&tree.root, || std::thread::sleep(Duration::from_millis(25))); - stop.store(true, std::sync::atomic::Ordering::SeqCst); - opened + let mut uj: u64 = 100_000; + RaplPackages::live_in(&tree.root, || { + uj += 4_000; + for dir in advancing { + tree.write(dir, &uj.to_string()); + } + std::thread::sleep(Duration::from_millis(25)); }) } let tree = two_sockets_with_psys_and_dram(); diff --git a/sandbox/sandbox-rs/crates/sandbox-meter/tests/process_energy.rs b/sandbox/sandbox-rs/crates/sandbox-meter/tests/process_energy.rs index 54185f97d..8cda244f9 100644 --- a/sandbox/sandbox-rs/crates/sandbox-meter/tests/process_energy.rs +++ b/sandbox/sandbox-rs/crates/sandbox-meter/tests/process_energy.rs @@ -109,8 +109,14 @@ fn the_process_energy_probe_is_live() { } /// 200 ms of one thread's CPU, burned in this process and metered through -/// `detect_in_process`, is a `Cpu` hardware reading between 0.2 J and 20 J, +/// `detect_in_process`, is a `Cpu` hardware reading between 0.1 J and 20 J, /// and inside the oracle's reading of the same work (read around the span). +/// +/// The floor is 0.1 J, not 0.2: 200 ms of CPU costs a different amount +/// depending on which core class ran it. On an M5 Max it read 0.18-1.24 J, +/// and 0.171 J on a busy host where the burn took 1 s of wall time (the +/// oracle read the same 0.171 J). 0.1 J is also the sleeping test's ceiling, +/// so no counter that reads wall time at a fixed power passes both. #[test] fn an_in_process_burn_is_a_hardware_reading_of_its_energy() { let _one = alone(); @@ -121,7 +127,7 @@ fn an_in_process_burn_is_a_hardware_reading_of_its_energy() { let outer = oracle_energy_nj(me()).unwrap() - outer_before; let uj = the_reading(&m); eprintln!("200 ms in-process burn: {:.4} J over {:?} wall (oracle around it: {:.4} J)", joules(uj), m.wall, outer as f64 / 1e9); - assert!((200_000..=20_000_000).contains(&uj), "{uj} µJ for 200 ms of one thread"); + assert!((100_000..=20_000_000).contains(&uj), "{uj} µJ for 200 ms of one thread"); assert!(uj <= outer / 1_000 + 1, "the span read more ({uj} µJ) than the oracle around it ({outer} nJ)"); }