Spec section 3.6 says the gauge polls MediaCmd::Stats from slice-5/seams MediaThread.
S4 standalone-path conclusion (per kickoff hard rule) means the SimCall wires itself
in tokio WITHOUT registering with the binary MediaThread -- no MediaCmd::Stats channel
to poll. This S6 implementation adapts: the gauge is wired INTO the SimCalls tick loop
directly via a shared Arc<TickLagStats> handle. SimCall::run_with_gauge records per-tick
wall-clock duration via Instant::now() measurement around the tick work (not including
the 20ms sleep -- matches MediaStats.last_tick_micros semantics).
Same conceptual metric (max tick lag + overrun count + pct), different source (in-process
tokio SimCall tick loop rather than binary MediaThread poll loop). Future slice (post-spearhead
refinement, paired with network-realism mode) wires the gauge against the binary MediaThread
per spec section 3.6 -- requires either MediaThread registration (the RegisterSim variant
forbidden this slice) OR a client-server sim mode (deferred per spec 8.6).
TickLagStats uses atomics (AtomicU64) -- 3 atomic ops per tick (CAS-max, conditional
fetch_add on overruns, unconditional fetch_add on total) -- rather than Mutex<Vec<Duration>>
which would lock the vector per tick + add ordering overhead significant to the per-tick
microseconds budget.
ConcurrencyRunner creates one shared Arc<TickLagStats> per concurrency level + passes
clones to each of the N SimCalls via run_with_gauge. After the sweep, reads the gauges
fields to populate PerConcurrencyReports tick-lag fields (max_tick_lag_micros, tick_overruns,
total_ticks, tick_overrun_pct) -- the ADR-0010 doctrine-drift detector.
SimCall::run(self) -> LatencyProbe is preserved as a convenience default that calls
run_with_gauge(TickLagStats::new()) (gauge data discarded); callers needing tick-lag
data (ConcurrencyRunner) use run_with_gauge directly.
Signed-off-by: Aaron D. Lee <himself@adlee.work>