energy tests: a 0.1 J burn floor, tick-driven fake RAPL trees, stale trees cleared #136
4 changed files with 47 additions and 41 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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<RaplPackages, ProbeError> {
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -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)");
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue