Class TraceFileCacheConstants
Compile-time constants for the cache format. Kept in one place so writer, reader, builder, and tests all agree.
public static class TraceFileCacheConstants
- Inheritance
-
TraceFileCacheConstants
- Inherited Members
Fields
ByteCap
Maximum uncompressed bytes per chunk — upper bound on how big a chunk can be in payload size.
public const int ByteCap = 1048576
Field Value
CacheFileExtension
Sidecar file extension, appended to the source path (e.g., foo.typhon-trace → foo.typhon-trace-cache).
public const string CacheFileExtension = "-cache"
Field Value
CurrentChunkerVersion
Current chunker policy version. Incremented when TickCap, ByteCap, or the fold logic changes in a way that
invalidates existing caches. Readers that see a different value must rebuild the cache.
v2: added TickSummary.StartUs.
v3: server-side async-completion fold — kickoff records carry the full async duration; completion records dropped from the stream.
v4: tick duration computed from TickStart→TickEnd wall time only (no span-endTs extension) so folded kickoffs whose end extends past
TickEnd don't bloat the summary and cause adjacent ticks to appear overlapping in the viewer's selection math.
v5: also stopped extending lastTs inside the fold path itself — there was a second duration-extension site that v4 missed. With this,
tick durations are purely wall-clock TickStart→TickEnd, regardless of whether fold fires within the chunk.
v6: pre-first-tick events (MemoryAllocEvent, GcStart, GcEnd, GcSuspension) are buffered and prepended to the first chunk's byte
stream instead of being silently dropped. Old caches built with v5 are missing engine-startup memory events that land before
the first TickStart — readers that see v5 must rebuild against a v6 builder to surface them.
v7: added TraceEventKind.ThreadInfo (kind 77). Emitted at slot claim — typically pre-first-tick — and added to the pre-tick
buffer path. Old v6 caches don't surface thread names; readers must rebuild against v7 to populate lane labels.
v8: two combined changes, both invalidating prior caches:
(a) EventCap as a tick-boundary chunk-close trigger — shrinks the worst-case per-chunk decode cost in dense
multi-tick regions that previously squeaked under ByteCap because of small record sizes.
(b) Intra-tick splitting — a single pathologically dense tick (e.g., 2 M events in one tick) can now be split across multiple
chunks via IntraTickByteCap / IntraTickEventCap. Continuation chunks are marked with
FlagIsContinuation. The decoder must seed its tick counter to FromTick directly for continuation chunks
(vs FromTick - 1 for normal chunks). The former ChunkManifestEntry.Padding u32 is now Flags; offset and
size are unchanged, so v7-on-disk entries read back with Flags=0 which correctly means "normal, non-continuation."
v9: TickSummary grew from 32 B to 40 B with four new fields — OverloadLevel, TickMultiplier,
MetronomeWaitUs, MetronomeIntentClass — captured by the builder from TickEnd payload (overload byte +
multiplier byte) and from observed SchedulerMetronomeWait (kind 241) spans. v8 caches don't carry these and must
rebuild against a v9 builder. Issue #289 follow-up: makes per-tick throttle decisions and metronome idle visible to the viewer.
v10: same on-disk layout as v9, but v9 caches were built while InspectorTickEnd still hardcoded
(overloadLevel: 0, tickMultiplier: 1) on the wire — meaning every v9 TickSummary on disk has zeroed
throttle fields regardless of actual engine state. Bumping the version forces those caches to rebuild from source so
the new TickEnd payload (with real values) is captured. No struct shape change — readers built against v10 should be
bit-compatible with v9 wire format if the source happened to be re-traced post-fix, but the version bump removes any
ambiguity for users who still hold v9 sidecars from earlier in the same dev cycle.
v11: TickSummary grew from 40 B to 44 B with two new fields — ConsecutiveOverrun + ConsecutiveUnderrun
(the OverloadDetector's per-tick streak counters, saturating u16). Captured by the builder from the
SchedulerOverloadDetector instant (kind 242). Drives the Workbench OverloadStrip tooltip so users can see the
deescalation streak climb toward 20 (or reset on any overrun) — the answer to "why didn't multiplier go down?".
v10 caches must rebuild against v11.
v12 (current): four new sections added (SystemTickSummaries, QueueTickSummaries,
PostTickSummaries, QueueNameTable) populated by
IncrementalCacheBuilder from existing wire events plus a new QueueTickEnd event for the per-queue path.
Drives the Workbench Data API v2 tracks (#311) — system/<name>, queue/<name>, posttick/*.
ActiveSystemsBitmask is no longer populated in v12 builds (zeroed) — the per-system rows in
SystemTickSummaries are the authoritative answer to "which systems ran in tick T", with no
u64 cap. The field is retained on disk for v11 reader back-compat but consumers should migrate. v11 caches must rebuild.
public const ushort CurrentChunkerVersion = 16
Field Value
Remarks
v13: SystemTickSummary grew a TotalCpuUs field — total CPU time consumed across all workers (sum of chunk durations),
distinct from DurationUs (wall-clock). Enables correct parallelism-inefficiency math in the workbench (A1/A2 in 09-system-dag.md)
without requiring per-chunk decode. v12 caches must rebuild.
v14: six new sections forwarded from the source's v7 static-structure tables —
ComponentDefinitions, ArchetypeDefinitions,
IndexCatalog, RuntimeConfig,
EventQueueCatalog, ResourceGraphSnapshot. Drives the Workbench schema
panels for trace sessions (SchemaBrowser, ArchetypeBrowser, SchemaIndexes, et al.). v13 caches must rebuild — and since the source
also bumped to v7, the source itself must be re-recorded; v6 source files are hard-rejected by the reader.
v15: one new section SystemArchetypeTouches folded from the new
SchedulerSystemArchetype wire event (kind 245). Drives the archetype/, system-archetype/, and
component-family/* track families in the Workbench Data Flow module (#327). v14 caches must rebuild.
v16 (current, 2026-05-10): no schema change, but NamedSpan reassigned from value 200 to 246
(was colliding with EcsQueryMaskAnd). Cached records reference the kind ID directly, so
v15 caches must rebuild to re-emit NamedSpan records under the new ID. v15 caches load against v8 source files would
mis-decode kind=200 records; bump enforces rebuild.
EventCap
Maximum events per chunk — closes a chunk at the next tick boundary when the running event count reaches this threshold. Complements ByteCap for regions where records are small and numerous (dense allocation bursts, high-frequency scheduler chunks, etc.): byte-cap alone could let tens of thousands of small records pile into one chunk that decodes slowly and dominates the client-side LRU budget. Splitting on event count instead caps the per-chunk decode cost at a bounded number of record decodes, regardless of their compressed byte ratio.
50 000 chosen empirically: at ~300 bytes average decoded per event (span + alloc mix), this is ~15 MB of resident heap per chunk — comfortable against the client's 500 MB LRU budget (30+ chunks headroom) and decodes in roughly 100-200 ms on a modern CPU. For ticks that fit under this cap, they're emitted as single whole chunks — no intra-tick splitting, no client-side merge.
public const int EventCap = 50000
Field Value
FingerprintEdgeBytes
Size of the prefix + suffix regions read from the source file to feed the fingerprint hash. 4 KB each side.
public const int FingerprintEdgeBytes = 4096
Field Value
FlagIsContinuation
Bit 0 of Flags — chunk starts mid-tick (continuation of the previous chunk's last tick).
public const uint FlagIsContinuation = 1
Field Value
IntraTickByteCap
Mid-tick byte cap. A SINGLE tick's accumulated record bytes exceeding this trigger the builder to close the current chunk
in the middle of the tick and start a new continuation chunk (marked with FlagIsContinuation). Deliberately
larger than ByteCap (2×) so that well-sized ticks never trip it — only genuinely pathological single ticks
(e.g., >1 MiB of records in one tick) get split, keeping the client-side mergeTickData path cold for the common case.
public const int IntraTickByteCap = 2097152
Field Value
IntraTickEventCap
Mid-tick event cap. Parallels IntraTickByteCap but counts records instead of bytes. A single tick whose event count hits this value triggers a mid-tick chunk close. 2× EventCap (100 000) so that a tick marginally over the normal event cap still emits as a single chunk — only genuinely pathological dense ticks split. Tuned independently of EventCap — they're separate dials so we can tighten one without affecting the other as workload data arrives.
public const int IntraTickEventCap = 100000
Field Value
TickCap
Maximum ticks per chunk — upper bound on how coarse a chunk can be in tick count.
public const int TickCap = 100