Document frame ring schema and limits of hitch attribution

This commit is contained in:
Matthew Stanley
2026-10-12 16:58:30 -07:00
parent 2cbca74483
commit 7d985d13bc
+14 -6
View File
@@ -1,25 +1,33 @@
# Always-on host wall-time rings (hitch attribution)
A gameplay hitch is wall time the emulation thread did not spend running the guest. These rings
record where it went, continuously from process start in every build, so a probe never arms
A gameplay hitch is an unusually long frame, whether from guest execution, host work, or waiting.
These rings record where the time went, continuously from process start in every build, so a probe never arms
anything: it queries a window that already happened.
| Ring | TCP command | Holds |
|------|-------------|-------|
| frame stamps | `frame_wall_ring {"from":SEQ,"n":N}` | one `[guest frame, wall us, interpreter insns (low 32 bits)]` per guest frame, 131072 frames (~36 min at 60 Hz) |
| frame stamps | `frame_wall_ring {"from":SEQ,"n":N}` | flat `f` array with `stride: 6`: `[guest frame, wall us, interpreter insns, static checks, process page faults, process read KiB]`; counters use their low 32 bits; 131072 frames (~36 min at 60 Hz) |
| spans | `host_span_ring {"from":SEQ,"n":N}` | `[seq, kind, t0_us, dur_us, frame, emu, arg]` for every named blocking-capable operation of 50 us or more, 65536 spans |
| host CPU sampler (debug-tools builds) | `host_profile {"frame_lo":A,"frame_hi":B}` | 1 kHz instruction-pointer samples of the emulation thread, 1 Mi samples (~17 min), with per-module totals for samples outside the exe |
Wall time is microseconds since the first ring call. `emu` is 1 for the emulation thread, 0 for a
worker (compiler child, publication preparer).
Readers and writers use nonblocking slot ownership. A contended sample may be dropped or skipped;
readers only return fully published records from the requested ring generation. A missing frame
stamp leaves an interval spanning multiple frames: do not report that interval as one long frame.
Legacy frame captures had three or four columns and no `stride`; consumers must identify their
layout before interpreting them. Process counters include worker threads, and page faults include
soft faults, so neither counter alone proves a blocking disk read.
Span kinds (`runtime/include/host_span_ring.h`): `autocapture_tick`, `provider_poll`,
`publish_commit`, `loader_rescan`, `dll_register`, `lazy_load` (a synchronous first map of a shard
at a dispatch miss), `capture_on_dma`, `capture_before_dma`, `ac_request` (spawning the compiler),
`ac_done_join`, `ac_prepare_image` (worker), `ac_child` (compiler lifetime, worker).
`ac_done_join`, `ac_prepare_image` (worker), `ac_child` (compiler lifetime, worker),
`overlay_dispatch`, `mod_work`, `frame_present`, and `disc_read` (cooked or raw sector read).
Reading a hitch: per frame, `wall delta - sum(emulation-thread spans overlapping the frame)` is
the time no span explains; then ask the host sampler for that frame window. A title's report tool
Reading a hitch: per frame, subtract the union of overlapping emulation-thread spans from the wall
delta to find the time no span explains; nested spans must not be counted twice. Then ask the host sampler for that frame window. A title's report tool
joins them (`LegendOfDragoonRecomp/tools/lod_hitch_report.py`).
Findings the rings produced (LoD, 2026-10-12): a toolchain staged from another emitter than the