jcp-energy: compare two threads' energy within one span, so host load no longer decides the test #179

Merged
dcharlot merged 12 commits from ci/energy-tests-load-independent into main 2026-10-07 21:21:28 -04:00

2026-10-07

Transaction Science
Merge origin/main (#166) into ci/energy-tests-load-independent
All checks were successful
energy meters on real hardware / RAPL and NVML on jetson-hub (pull_request) Successful in 32s
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 6m14s
standards workspaces build + test / aac-rs (pull_request) Successful in 1m48s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 1m23s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 1m25s
standards workspaces build + test / map-rs (pull_request) Successful in 1m37s
standards workspaces build + test / mesh-rs (pull_request) Successful in 1m2s
standards workspaces build + test / openpay (pull_request) Successful in 5m8s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 3m26s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 5m25s
jouleclaw public surface must stay documented / Public-surface audit (jouleclaw-rs) (pull_request) Successful in 57s
standards workspaces build + test / arl-rs (pull_request) Successful in 1m8s
standards workspaces build + test / eoc-rs (pull_request) Successful in 2m2s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m33s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 1m45s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 1m9s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m17s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m11s
standards workspaces build + test / sense-rs (pull_request) Successful in 1m6s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 3m40s
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-07 14:29:34 -04:00
Transaction Science
Merge origin/main (ab9650d5) into ci/energy-tests-load-independent
Some checks failed
energy meters on real hardware / RAPL and NVML on jetson-hub (pull_request) Successful in 33s
standards workspaces build + test / aac-rs (pull_request) Failing after 28s
standards workspaces build + test / joule-code-rs (pull_request) Failing after 28s
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 28m50s
standards workspaces build + test / map-rs (pull_request) Failing after 26s
standards workspaces build + test / mesh-rs (pull_request) Successful in 48s
standards workspaces build + test / openpay (pull_request) Successful in 5m41s
standards workspaces build + test / joulecontract-rs (pull_request) Failing after 17m2s
jouleclaw public surface must stay documented / Public-surface audit (jouleclaw-rs) (pull_request) Successful in 1m1s
standards workspaces build + test / arl-rs (pull_request) Successful in 1m14s
standards workspaces build + test / eoc-rs (pull_request) Successful in 1m58s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m31s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 1m9s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 1m10s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m14s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m11s
standards workspaces build + test / sense-rs (pull_request) Successful in 1m6s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 3m39s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 14m56s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 15m37s
Brings the branch up to date with main (past ade3f855) before review.
Main changed none of the files this branch changes, so the merge is
clean.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-07 09:15:29 -04:00
Transaction Science
Revert "jouleclaw-energy: end the child window on the child's energy, not 300 ms"
This reverts commit 85aab023. PR #166 (energy/load-robust-tests) fixes
the same test, jouleclaw-energy/tests/process_cpu.rs, the same way: it
reads the child's own counter until it reaches 0.2 J, for at least
300 ms and at most 5 s. Dropping this branch's copy of that fix keeps
the two PRs from conflicting on the file; #166 carries it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-07 09:15:12 -04:00

2026-10-06

Transaction Science
Merge origin/main (6205b38f) into ci/energy-tests-load-independent
Brings the branch up to date with main before review. No file this
branch changes was touched on main, so the merge is clean.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-06 18:16:58 -04:00
Transaction Science
jouleclaw-energy: the probe ticks burn their whole 25 ms of thread CPU
Both liveness ticks in this crate had a 500 ms wall-time cap. One is
liveness::burn_thread_cpu, used by the shared probe (the per-process,
RAPL and NVML counters). The other is apple_ioreport's own
burn_thread_cpu, used by the IOReport rail probe. Each burned its tick of
the probing thread's CPU time, but gave up after 500 ms of wall time. On
a host that runs the thread for under 5% of a core, the cap cut the tick
short. A probe that burns less between samples can see a live counter
hold, and call it frozen. The meter's verdict then depends on host load.
This is the defect fixed in proof-cascade's tick.

Production change: both are bounded by thread CPU time only. A tick
ends once the thread has burned the whole amount, however much wall time
that takes. Against a thread CPU clock that stops moving, it ends after
liveness::STUCK_CLOCK_ROUNDS (40,000) spin rounds in a row without the
clock moving, a count of the thread's own work rather than of wall time.
With no readable thread clock each still spins its amount of wall time,
as before. The wall_cap parameter and PROBE_TICK_WALL_CAP are gone; the
tests that passed a cap to the burn as a hang guard pass only the CPU
amount now.

Tests: apple_ioreport's assert_tick drops its escape clause
(`|| wall >= TEST_TICK_WALL_CAP`) and requires 25 ms of thread CPU from
every tick, however long it took.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-06 17:58:24 -04:00
Transaction Science
jcp-energy: the probe tick burns its whole 25 ms of thread CPU
The liveness probes' tick (liveness::burn_tick, which the per-process,
IOReport and RAPL probes all use) burned 25 ms of the probing thread's
CPU time but gave up after 500 ms of wall time. On a host that runs the
thread for under 5% of a core, the cap cut the tick short. A probe that
burns less between samples can see a live counter hold, and call it
frozen. The meter's verdict then depends on host load. This is the
defect fixed in proof-cascade's tick, in this crate's own copy.

Production change: liveness::burn is bounded by thread CPU time only.
The tick ends once the thread has burned the whole 25 ms, however much
wall time that takes. Against a thread CPU clock that stops moving it
ends after 40,000 spin rounds in a row without the clock moving, a count
of the thread's own work rather than of wall time. With no readable
thread clock it still spins 500 ms of wall time, as before; the constant
is renamed PROBE_TICK_NO_CLOCK_WALL for that remaining role.

Tests: assert_ticks_burned drops its escape clause
(`|| wall >= TICK_WALL_CAP`) and requires 25 ms of thread CPU from every
tick. The real-probe test requires a whole tick of CPU per interval
unconditionally, where it allowed half a tick whenever a tick was slow.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-06 17:58:24 -04:00
Transaction Science
proof-cascade: the probe tick burns its whole 25 ms of thread CPU
The liveness probe's tick burned 25 ms of the probing thread's CPU time
but gave up after 500 ms of wall time. On a loaded host that runs the
thread for under 5% of a core, the cap cut the tick short: on the macOS
runner an interval held 12.4 ms of thread CPU, under the tests' 12.5 ms
floor, and the host_status_calls_*_frozen tests failed. In production
the same cap made the probe's work per interval, and so its verdict,
depend on host load.

Production change: the tick is bounded by thread CPU time only. It ends
once the thread has burned the whole 25 ms, however much wall time that
takes. Against a thread CPU clock that stops moving it ends after 10,000
spin rounds in a row without the clock moving, a count of the thread's
own work rather than of wall time. An unreadable clock still falls back
to 25 ms of wall time.

Tests: R1 (each_tick_burns_the_probe_tick_of_thread_cpu_time) now
requires 25 ms of thread CPU from every tick. Its escape clause for a
tick that hit the wall cap is gone; that clause let a tick that only
slept for 500 ms pass. The R2 floor is unchanged, and its message no
longer cites the cap.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-06 17:40:42 -04:00
Transaction Science
jouleclaw-energy: end the child window on the child's energy, not 300 ms
the_process_counter_meters_this_process_and_nothing_else let a busy
child run for a fixed 300 ms of wall time and required its own counter
to rise 0.2 J. How much CPU a child gets in 300 ms depends on host load:
on the loaded macOS runner it spent 0.12 J and the test failed, though
nothing about the counter under test was wrong.

The window now lasts until the child's own count, read independently,
has risen 0.2 J, and at least 300 ms. A loaded host stretches the window
but always leaves 0.2 J of the child's work in it, twice the 0.1 J
ceiling on what this process may meter meanwhile, so the child-exclusion
check still has the same margin. A child that has not spent 0.2 J in
20 s fails the test: it did not work, or the host is too loaded to
tell.

Test-only change; no production code changed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-06 17:40:42 -04:00
Transaction Science
jcp-energy: compare two threads' energy within one span, not across two
two_threads_burning_meter_more_than_one metered one thread's 200 ms of
CPU in one span and two threads' in a second span, then required the
second figure to be larger. The CPU work in each span is fixed by the
threads' own CPU clocks, but its energy is not: it follows the core type
and clock the scheduler gives each span. On the loaded macOS runner one
thread read 0.92 J and two threads 0.59 J, and the check failed though
the meter counted every thread.

Both threads now burn in one span, and a per-thread oracle (the tests'
own proc_pidinfo PROC_PIDTHREADCOUNTS read of each thread's energy
count) reads each one's rise across its burn. The span's figure must be
at least the two rises added up, and more than either one's alone. The
figures compared cover the same work at the same time, so host load
cannot move one against the other. The check is stronger than before:
it bounds the figure by both threads' whole counts, not only by another
span's figure. A meter that reads only the calling thread fails it.

Test-only change; no production code changed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-06 17:40:42 -04:00