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:
+14
-1
@@ -1757,6 +1757,11 @@ fn schedule_loop(inner: &Arc<RuntimeInner>, slot_idx: usize) {
|
||||
let gen = pid.generation();
|
||||
match intent {
|
||||
YieldIntent::Yield => {
|
||||
// RFC 007 audit: measure the sample tail this yield drops
|
||||
// (near-zero for slice-expiry yields, which sample at the
|
||||
// same checkpoint; fat for explicit yield_now in-site).
|
||||
#[cfg(feature = "smarm-causal")]
|
||||
crate::causal::on_deschedule(slot, false);
|
||||
// Running OR RunningNotified → Queued; a notification
|
||||
// arriving mid-run coalesces into the re-queue.
|
||||
crate::te!(crate::trace::Event::Yield(pid));
|
||||
@@ -1765,6 +1770,10 @@ fn schedule_loop(inner: &Arc<RuntimeInner>, slot_idx: usize) {
|
||||
}
|
||||
YieldIntent::Park => {
|
||||
if slot.word.park_return(gen) {
|
||||
// RFC 007 audit: an in-site park drops its sample
|
||||
// tail (nothing flushes it; on_resume re-arms).
|
||||
#[cfg(feature = "smarm-causal")]
|
||||
crate::causal::on_deschedule(slot, true);
|
||||
// RFC 007: a real park — the eventual wake forgives
|
||||
// delay accrued while blocked (the instant-wake
|
||||
// re-queue below does NOT: that actor never blocked).
|
||||
@@ -1774,7 +1783,11 @@ fn schedule_loop(inner: &Arc<RuntimeInner>, slot_idx: usize) {
|
||||
} else {
|
||||
// An unpark landed in the prep-to-park window; the
|
||||
// word is back to Queued — re-queue instead of
|
||||
// parking. The lost-wakeup window, closed.
|
||||
// parking. The lost-wakeup window, closed: this actor
|
||||
// never blocked, so its dropped tail counts as a
|
||||
// yield in the RFC 007 audit.
|
||||
#[cfg(feature = "smarm-causal")]
|
||||
crate::causal::on_deschedule(slot, false);
|
||||
crate::te!(crate::trace::Event::UnparkFlagConsumed(pid));
|
||||
inner.enqueue(pid);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user