Table of Contents

Class TraceFileCacheConstants

Namespace
Typhon.Profiler
Assembly
Typhon.Profiler.dll

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

int

CacheFileExtension

Sidecar file extension, appended to the source path (e.g., foo.typhon-tracefoo.typhon-trace-cache).

public const string CacheFileExtension = "-cache"

Field Value

string

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

ushort

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

int

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

int

FlagIsContinuation

Bit 0 of Flags — chunk starts mid-tick (continuation of the previous chunk's last tick).

public const uint FlagIsContinuation = 1

Field Value

uint

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

int

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

int

TickCap

Maximum ticks per chunk — upper bound on how coarse a chunk can be in tick count.

public const int TickCap = 100

Field Value

int