energy tests: hold under a host loaded past a hundred runnable threads #166
2 changed files with 45 additions and 8 deletions
|
|
@ -463,6 +463,10 @@ mod rail_liveness {
|
||||||
pub(crate) opened: bool,
|
pub(crate) opened: bool,
|
||||||
/// Ticks run between samples.
|
/// Ticks run between samples.
|
||||||
pub(crate) ticks: u32,
|
pub(crate) ticks: u32,
|
||||||
|
/// Each tick's wall time, in order. Recorded, never decided on: a test
|
||||||
|
/// reads it to tell a tick that its wall cap cut short, on a host that
|
||||||
|
/// gave the thread almost no CPU, from one that did not burn at all.
|
||||||
|
pub(crate) tick_walls: Vec<Duration>,
|
||||||
/// Every sample call, in order.
|
/// Every sample call, in order.
|
||||||
pub(crate) calls: Vec<SampleCall>,
|
pub(crate) calls: Vec<SampleCall>,
|
||||||
/// The rule, folded over every sample that was taken.
|
/// The rule, folded over every sample that was taken.
|
||||||
|
|
@ -493,9 +497,9 @@ mod rail_liveness {
|
||||||
S: FnMut() -> Option<Sample>,
|
S: FnMut() -> Option<Sample>,
|
||||||
{
|
{
|
||||||
let Some(mut sampler) = open() else {
|
let Some(mut sampler) = open() else {
|
||||||
return ProbeRun { opened: false, ticks: 0, calls: Vec::new(), watch: RailWatch::default() };
|
return ProbeRun { opened: false, ticks: 0, tick_walls: Vec::new(), calls: Vec::new(), watch: RailWatch::default() };
|
||||||
};
|
};
|
||||||
let (mut calls, mut ticks) = (Vec::new(), 0u32);
|
let (mut calls, mut ticks, mut tick_walls) = (Vec::new(), 0u32, Vec::new());
|
||||||
let watch = watch(
|
let watch = watch(
|
||||||
|| {
|
|| {
|
||||||
let (cpu_before, started) = (thread_cpu(), wall());
|
let (cpu_before, started) = (thread_cpu(), wall());
|
||||||
|
|
@ -512,12 +516,14 @@ mod rail_liveness {
|
||||||
},
|
},
|
||||||
|| {
|
|| {
|
||||||
ticks += 1;
|
ticks += 1;
|
||||||
|
let at = wall();
|
||||||
tick();
|
tick();
|
||||||
|
tick_walls.push(wall().saturating_sub(at));
|
||||||
},
|
},
|
||||||
&wall,
|
&wall,
|
||||||
budget,
|
budget,
|
||||||
);
|
);
|
||||||
ProbeRun { opened: true, ticks, calls, watch }
|
ProbeRun { opened: true, ticks, tick_walls, calls, watch }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The status a probe verdict gives this host. No verdict — no
|
/// The status a probe verdict gives this host. No verdict — no
|
||||||
|
|
@ -3142,7 +3148,26 @@ mod macos_ioreport {
|
||||||
);
|
);
|
||||||
assert!(ticks.len() >= 2, "the probe made only {} sample calls", run.calls.len());
|
assert!(ticks.len() >= 2, "the probe made only {} sample calls", run.calls.len());
|
||||||
assert_eq!(run.ticks as usize, ticks.len(), "every tick runs between two sample calls");
|
assert_eq!(run.ticks as usize, ticks.len(), "every tick runs between two sample calls");
|
||||||
|
assert_eq!(run.tick_walls.len(), ticks.len(), "every tick's wall time is recorded");
|
||||||
for (i, t) in ticks.iter().enumerate() {
|
for (i, t) in ticks.iter().enumerate() {
|
||||||
|
// A tick may burn less only where its wall cap cut it short: a
|
||||||
|
// host loaded past a hundred runnable threads gave this thread
|
||||||
|
// under 2% of a core for 500 ms (9.6 ms of CPU, load average
|
||||||
|
// 101). A tick that stopped short of both its CPU and its wall
|
||||||
|
// time waited or skipped, and fails.
|
||||||
|
// A tick cut short still spun whenever it ran, so it burned
|
||||||
|
// some CPU. A tick that slept through its 500 ms burned next to
|
||||||
|
// none, and fails either way.
|
||||||
|
if run.tick_walls[i] >= crate::clocks::PROBE_TICK_WALL_CAP {
|
||||||
|
eprintln!("ioreport probe tick {i}: {t:?} of CPU, cut short by its {:?} wall cap", run.tick_walls[i]);
|
||||||
|
assert!(
|
||||||
|
*t >= TICK / 25,
|
||||||
|
"tick {i} ran its whole {:?} wall cap and burned only {t:?} of this thread's CPU: it waited, \
|
||||||
|
not spun",
|
||||||
|
run.tick_walls[i]
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
assert!(
|
assert!(
|
||||||
*t >= TICK / 2,
|
*t >= TICK / 2,
|
||||||
"tick {i} burned only {t:?} of this thread's CPU (every tick: {ticks:?}): the probe must burn \
|
"tick {i} burned only {t:?} of this thread's CPU (every tick: {ticks:?}): the probe must burn \
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,11 @@ fn the_process_counter_meters_this_process_and_nothing_else() {
|
||||||
|
|
||||||
// 5. A child process's work is not attributed: a child burns CPU while this
|
// 5. A child process's work is not attributed: a child burns CPU while this
|
||||||
// process sleeps, and is killed and reaped inside the window. The child's
|
// process sleeps, and is killed and reaped inside the window. The child's
|
||||||
// own counter, read independently, shows the work happened.
|
// own counter, read independently, shows the work happened. The child
|
||||||
|
// runs until its own counter has read 0.2 J (at least 300 ms, at most
|
||||||
|
// 5 s), not for a fixed 300 ms: on a heavily loaded host a fixed 300 ms
|
||||||
|
// got the child too little CPU (0.076 J at a load average of 170), and
|
||||||
|
// the test failed on the host's load, not on the meter.
|
||||||
let mut child = Command::new("/bin/sh")
|
let mut child = Command::new("/bin/sh")
|
||||||
.args(["-c", "while :; do :; done"])
|
.args(["-c", "while :; do :; done"])
|
||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
|
|
@ -111,16 +115,24 @@ fn the_process_counter_meters_this_process_and_nothing_else() {
|
||||||
.expect("spawn a busy child");
|
.expect("spawn a busy child");
|
||||||
let pid = child.id() as libc::pid_t;
|
let pid = child.id() as libc::pid_t;
|
||||||
let child_before = energy_nj_of(pid);
|
let child_before = energy_nj_of(pid);
|
||||||
|
let started = Instant::now();
|
||||||
let (child_burned_nj, parent) = counter.measure(|| {
|
let (child_burned_nj, parent) = counter.measure(|| {
|
||||||
std::thread::sleep(Duration::from_millis(300));
|
let mut burned = 0;
|
||||||
let burned = energy_nj_of(pid) - child_before;
|
while started.elapsed() < Duration::from_secs(5) {
|
||||||
|
std::thread::sleep(Duration::from_millis(50));
|
||||||
|
burned = energy_nj_of(pid) - child_before;
|
||||||
|
if burned >= 200_000_000 && started.elapsed() >= Duration::from_millis(300) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
child.kill().expect("kill the child");
|
child.kill().expect("kill the child");
|
||||||
child.wait().expect("reap the child");
|
child.wait().expect("reap the child");
|
||||||
burned
|
burned
|
||||||
});
|
});
|
||||||
println!(
|
println!(
|
||||||
"process-cpu: child burned {:.3} J in 300 ms; this process's window = {parent:?} µJ",
|
"process-cpu: child burned {:.3} J in {:?}; this process's window = {parent:?} µJ",
|
||||||
child_burned_nj as f64 / 1e9
|
child_burned_nj as f64 / 1e9,
|
||||||
|
started.elapsed()
|
||||||
);
|
);
|
||||||
assert!(child_burned_nj >= 200_000_000, "the child did not burn: {child_burned_nj} nJ");
|
assert!(child_burned_nj >= 200_000_000, "the child did not burn: {child_burned_nj} nJ");
|
||||||
match parent {
|
match parent {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue