feat(causal): ledger audit — decompose the @50 injection deficit (RFC 007)

Measure-only counters for the deficit hunt (~23ms short per 700ms window
at 50% on the bottleneck site; superlinear vs 25%). Nothing here changes
injection or absorption; the sweep decides the fix.

Buckets, windowed per cell into new ExperimentResult fields (audit
snapshot taken at end() — spin/attribution freeze there, forgiveness
does not):
- spin_absorbed / park_forgiven: where owed delay actually went. Spin
  during a 0% cell is the baseline-contamination signature — leftover
  debt from a prior window being paid in a later one (checks gate on
  the experiment word, so cooldowns pay nothing and debt carries over).
- drop_park / drop_yield (+counts): the deschedule path flushes no
  sample tail — an in-target-site park or yield silently loses
  [last sample -> now]; on_resume re-arms before the actor runs again.
  New on_deschedule hook in all three intent arms (real park; explicit/
  slice-expiry yield; requeued park counts as yield — it never blocked).
  Slice-expiry yields sample at the descheduling checkpoint, so a fat
  yield bucket points at explicit yield_now or requeued parks.
- discard_overmax (+count, in would-be delta terms so columns compare
  against injected_cycles) / discard_unarmed: the attribute() clamps,
  previously silent.

LedgerCounters + ledger_counters() expose cumulative totals (tests,
run-level prints); render_ledger_audit() is the per-cell companion to
render_summary, which stays byte-identical (pinned). ExperimentResult
now derives Default so literals survive future audit-field growth.

Tests: +7 (spin counted, forgiveness counted, in-site park drop, in-site
yield drop, overmax discard, 0%-window leftover absorption — synthesized
deterministically via inject_delay_cycles_for_test with no experiment
active — and the audit render). 22/22 causal.
This commit is contained in:
Claude (sandbox)
2026-07-13 12:07:19 +00:00
parent a2d0b7af18
commit a3be8f0977
3 changed files with 482 additions and 8 deletions
+257
View File
@@ -261,6 +261,7 @@ fn impact_from_synthetic_results() {
duration: secs(1.0),
deltas: vec![("units".into(), 1000)],
injected_cycles: 0,
..Default::default()
},
// Bottleneck at 50%: raw count unchanged, 0.5s injected
// -> rate 1000/0.5 = 2x baseline -> +100%.
@@ -270,6 +271,7 @@ fn impact_from_synthetic_results() {
duration: secs(1.0),
deltas: vec![("units".into(), 1000)],
injected_cycles: cycles(0.5),
..Default::default()
},
ExperimentResult {
site: "overlapped".into(),
@@ -277,6 +279,7 @@ fn impact_from_synthetic_results() {
duration: secs(1.0),
deltas: vec![("units".into(), 1000)],
injected_cycles: 0,
..Default::default()
},
// Overlapped at 50%: everyone else (incl. the real bottleneck)
// slowed, count drops with the divisor -> ~0% impact.
@@ -286,6 +289,7 @@ fn impact_from_synthetic_results() {
duration: secs(1.0),
deltas: vec![("units".into(), 500)],
injected_cycles: cycles(0.5),
..Default::default()
},
];
let bn = impact_pct(&results, "bottleneck", 50, "units").unwrap();
@@ -652,3 +656,256 @@ fn wall_send_after_cancels() {
"cancelled wall send_after delivered"
);
}
// ---------------------------------------------------------------------------
// Ledger audit (RFC 007 deficit hunt) — measure-only counters that decompose
// where injected delay is born, paid, forgiven, and where would-be
// attribution is silently lost. Behaviour under test is accounting only:
// none of these tests assert a change in injection or absorption itself.
// ---------------------------------------------------------------------------
/// A never-parking bystander's spin absorption lands in the audit's
/// `spin_absorbed_cycles` — the "paid for real" bucket.
#[test]
fn audit_counts_spin_absorption() {
let _s = serial();
smarm::init(smarm::Config::exact(1)).run(|| {
let stop = Arc::new(AtomicBool::new(false));
let stop_t = stop.clone();
let target = smarm::spawn(move || {
let _g = smarm::causal_site!("audit-spin-site");
while !stop_t.load(Ordering::Relaxed) {
smarm::check!();
}
});
let stop_b = stop.clone();
let bystander = smarm::spawn(move || {
while !stop_b.load(Ordering::Relaxed) {
smarm::check!();
}
});
let c0 = smarm::causal::ledger_counters();
let g0 = smarm::causal::global_delay_cycles();
smarm::causal::begin_experiment_for_test("audit-spin-site", 50);
smarm::sleep(Duration::from_millis(150));
smarm::causal::end_experiment_for_test();
let injected = smarm::causal::global_delay_cycles() - g0;
let c1 = smarm::causal::ledger_counters();
stop.store(true, Ordering::Relaxed);
target.join().unwrap();
bystander.join().unwrap();
let spun = c1.spin_absorbed_cycles - c0.spin_absorbed_cycles;
assert!(injected > 0, "experiment injected nothing");
assert!(
spun >= injected / 2,
"audit missed spin absorption: {spun} of {injected} injected"
);
// Payers are bounded by the debt that exists; 2x headroom covers the
// main actor's incidental checkpoint payments.
assert!(
spun <= injected.saturating_mul(2),
"audit over-counted spin: {spun} of {injected} injected"
);
});
}
/// A wake after a real park waives the sleeper's accrued debt; the waiver is
/// tallied in `park_forgiven_cycles` (Coz's blocked-thread rule, now visible).
#[test]
fn audit_counts_park_forgiveness() {
let _s = serial();
smarm::init(smarm::Config::exact(1)).run(|| {
let stop = Arc::new(AtomicBool::new(false));
let stop_t = stop.clone();
let target = smarm::spawn(move || {
let _g = smarm::causal_site!("audit-forgive-site");
while !stop_t.load(Ordering::Relaxed) {
smarm::check!();
}
});
// Parks across the whole injection window; owes everything at wake.
let sleeper = smarm::spawn(|| smarm::sleep(Duration::from_millis(120)));
let c0 = smarm::causal::ledger_counters();
let g0 = smarm::causal::global_delay_cycles();
smarm::causal::begin_experiment_for_test("audit-forgive-site", 50);
smarm::sleep(Duration::from_millis(60));
smarm::causal::end_experiment_for_test();
let injected = smarm::causal::global_delay_cycles() - g0;
sleeper.join().unwrap();
let c1 = smarm::causal::ledger_counters();
stop.store(true, Ordering::Relaxed);
target.join().unwrap();
let forgiven = c1.park_forgiven_cycles - c0.park_forgiven_cycles;
assert!(injected > 0, "experiment injected nothing");
assert!(
forgiven >= injected / 2,
"audit missed park forgiveness: {forgiven} of {injected} injected"
);
});
}
/// Parking while *inside* the target site drops the sample tail
/// `[last sample -> park]` — nothing flushes it, and `on_resume` re-arms the
/// clock. The audit counts that loss without changing it.
#[test]
fn audit_counts_park_drop_inside_target_site() {
let _s = serial();
smarm::init(smarm::Config::exact(1)).run(|| {
smarm::causal::begin_experiment_for_test("audit-park-drop-site", 50);
let c0 = smarm::causal::ledger_counters();
let h = smarm::spawn(|| {
let _g = smarm::causal_site!("audit-park-drop-site");
smarm::check!(); // arm an in-site sample interval
smarm::sleep(Duration::from_millis(20)); // real park, in-guard
});
h.join().unwrap();
let c1 = smarm::causal::ledger_counters();
smarm::causal::end_experiment_for_test();
assert!(
c1.drop_park_n > c0.drop_park_n,
"in-site park recorded no drop event"
);
assert!(
c1.drop_park_cycles > c0.drop_park_cycles,
"in-site park drop carried no cycles"
);
});
}
/// An explicit yield inside the target site loses its tail the same way; it
/// lands in the separate yield bucket so park- and yield-losses stay apart.
#[test]
fn audit_counts_yield_drop_inside_target_site() {
let _s = serial();
smarm::init(smarm::Config::exact(1)).run(|| {
smarm::causal::begin_experiment_for_test("audit-yield-drop-site", 50);
let c0 = smarm::causal::ledger_counters();
let h = smarm::spawn(|| {
let _g = smarm::causal_site!("audit-yield-drop-site");
smarm::check!(); // arm an in-site sample interval
smarm::yield_now();
smarm::check!();
});
h.join().unwrap();
let c1 = smarm::causal::ledger_counters();
smarm::causal::end_experiment_for_test();
assert!(
c1.drop_yield_n > c0.drop_yield_n,
"in-site yield recorded no drop event"
);
assert!(
c1.drop_yield_cycles > c0.drop_yield_cycles,
"in-site yield drop carried no cycles"
);
});
}
/// A single in-site interval past MAX_SAMPLE_CYCLES is discarded by the
/// TSC-weirdness clamp; the audit counts the discard instead of losing it
/// silently.
#[test]
fn audit_counts_overmax_discard() {
let _s = serial();
smarm::init(smarm::Config::exact(1)).run(|| {
smarm::causal::begin_experiment_for_test("audit-overmax-site", 50);
let c0 = smarm::causal::ledger_counters();
let h = smarm::spawn(|| {
let _g = smarm::causal_site!("audit-overmax-site");
// Stay in-site past MAX_SAMPLE_CYCLES (~33ms at 3GHz) without a
// single checkpoint: no allocs, no check!. The guard-drop flush
// then sees one huge interval and must discard it.
let t0 = std::time::Instant::now();
while t0.elapsed() < Duration::from_millis(60) {
std::hint::spin_loop();
}
});
h.join().unwrap();
let c1 = smarm::causal::ledger_counters();
smarm::causal::end_experiment_for_test();
assert!(
c1.discard_overmax_n > c0.discard_overmax_n,
"overmax interval recorded no discard event"
);
assert!(
c1.discard_overmax_cycles > c0.discard_overmax_cycles,
"overmax discard carried no cycles"
);
});
}
/// Debt left outstanding when a window ends is paid during the NEXT active
/// experiment — including a 0% baseline cell, whose bystander-chase branch is
/// live at pct=0. Synthesized deterministically: grow the ledger with no
/// experiment active (nobody pays), then watch a 0% window absorb it.
#[test]
fn audit_zero_pct_window_absorbs_leftover_debt() {
let _s = serial();
smarm::init(smarm::Config::exact(1)).run(|| {
let hz = smarm::causal::tsc_hz();
let stop = Arc::new(AtomicBool::new(false));
let stop_b = stop.clone();
let bystander = smarm::spawn(move || {
while !stop_b.load(Ordering::Relaxed) {
smarm::check!();
}
});
// No experiment active: checks early-return, debt sits unpaid.
let debt = (hz * 0.020) as u64; // ~20ms
smarm::causal::inject_delay_cycles_for_test(debt);
smarm::sleep(Duration::from_millis(30));
let c0 = smarm::causal::ledger_counters();
let g0 = smarm::causal::global_delay_cycles();
smarm::causal::begin_experiment_for_test("audit-zero-pct-site", 0);
smarm::sleep(Duration::from_millis(60));
smarm::causal::end_experiment_for_test();
let c1 = smarm::causal::ledger_counters();
stop.store(true, Ordering::Relaxed);
bystander.join().unwrap();
assert_eq!(
smarm::causal::global_delay_cycles(),
g0,
"a 0% window must inject nothing"
);
let spun = c1.spin_absorbed_cycles - c0.spin_absorbed_cycles;
assert!(
spun >= debt / 2,
"leftover debt not absorbed in the 0% window: {spun} of {debt}"
);
});
}
/// The audit render lists one line per cell with the ledger columns.
#[test]
fn ledger_audit_renders_per_cell() {
use smarm::causal::{render_ledger_audit, ExperimentResult};
let results = vec![ExperimentResult {
site: "audit-render-site".into(),
speedup_pct: 50,
duration: Duration::from_secs(1),
deltas: vec![("units".into(), 10)],
injected_cycles: 1_000_000,
spin_absorbed_cycles: 900_000,
park_forgiven_cycles: 50_000,
drop_park_cycles: 30_000,
drop_park_n: 2,
drop_yield_cycles: 10_000,
drop_yield_n: 1,
discard_overmax_cycles: 5_000,
discard_overmax_n: 1,
discard_unarmed_n: 0,
}];
let s = render_ledger_audit(&results);
assert!(s.contains("ledger audit"), "missing header: {s}");
assert!(s.contains("audit-render-site"), "missing site line: {s}");
assert!(s.contains("absorbed"), "missing absorbed column: {s}");
assert!(s.contains("forgiven"), "missing forgiven column: {s}");
}