Class TickTelemetryRing
Pre-allocated circular buffer for tick telemetry. Zero allocation on Record(in TickTelemetry, ReadOnlySpan<SystemTelemetry>). Single writer (tick driver thread), multiple readers (diagnostics, metrics).
public sealed class TickTelemetryRing
- Inheritance
-
TickTelemetryRing
- Inherited Members
Remarks
Default capacity: 1024 entries (~17 seconds at 60Hz). Memory: 1024 × (sizeof(TickTelemetry) + systemCount × sizeof(SystemTelemetry)). For a 20-system DAG: ~1024 × (32 + 20 × 48) ≈ ~1 MB.
Constructors
TickTelemetryRing(int, int)
Creates a new telemetry ring buffer.
public TickTelemetryRing(int capacity, int systemCount)
Parameters
Properties
Capacity
Ring buffer capacity.
public int Capacity { get; }
Property Value
NewestTick
Tick number of the newest recorded entry, or -1 if no ticks recorded yet.
public long NewestTick { get; }
Property Value
OldestAvailableTick
Tick number of the oldest available entry, or -1 if no ticks recorded yet.
public long OldestAvailableTick { get; }
Property Value
TotalTicksRecorded
Number of ticks recorded so far (may exceed capacity — only last Capacity are retained).
public long TotalTicksRecorded { get; }
Property Value
Methods
GetSystemMetrics(long)
Returns the per-system telemetry for a given tick number.
public ReadOnlySpan<SystemTelemetry> GetSystemMetrics(long tickNumber)
Parameters
tickNumberlongAbsolute tick number.
Returns
- ReadOnlySpan<SystemTelemetry>
Span over the system metrics array for this tick.
Exceptions
- ArgumentOutOfRangeException
Tick is not in the buffer.
GetTick(long)
Returns the tick telemetry for a given tick number.
public ref readonly TickTelemetry GetTick(long tickNumber)
Parameters
tickNumberlongAbsolute tick number.
Returns
- TickTelemetry
Reference to the stored telemetry. Only valid if the tick is still in the buffer.
Exceptions
- ArgumentOutOfRangeException
Tick is not in the buffer (too old or not yet recorded).
Record(in TickTelemetry, ReadOnlySpan<SystemTelemetry>)
Records a tick's telemetry data into the ring buffer. Called at the end of each tick by the scheduler. Zero allocation — copies data into pre-allocated slots.
public void Record(in TickTelemetry tick, ReadOnlySpan<SystemTelemetry> systems)
Parameters
tickTickTelemetryTick-level telemetry.
systemsReadOnlySpan<SystemTelemetry>Per-system telemetry for this tick. Length must equal systemCount.
TryGetRange(long, out long, out long)
The readable tick range at or after fromInclusive, or false when there is none.
public bool TryGetRange(long fromInclusive, out long first, out long last)
Parameters
fromInclusivelongThe oldest tick the caller wants. Clamped up to OldestAvailableTick; the caller owns what "the window" means, so this takes an absolute tick rather than a width and never reinterprets one.
firstlongThe oldest readable tick in the range. Undefined when this returns false.
lastlongThe newest readable tick in the range — NewestTick. Undefined when this returns false.
Returns
- bool
true when
for (var t = first; t <= last; t++)is safe to pass to GetTick(long).
Remarks
Every window pass over this ring should start here, because the hand-written form has a trap and three shipped copies of it fell in.
OldestAvailableTick and NewestTick both report -1 while the ring is empty, so the natural
for (var t = Math.Max(oldest, tick - window); t <= newest; t++) evaluates to t = -1; -1 <= -1 and calls
GetTick(long)(-1), which throws. Empty is not an edge case: the ring is written at the END of a tick, so it is the state every consumer sees
on tick 0.
That throw is unusually destructive on a tick-path consumer and is why this exists rather than a note in each caller.
StatsEncoder's three passes are reached from SubscriptionsContext.Reset, which runs BEFORE the ring is written — so the throw stopped the
recording that would have made the next tick's call legal, and the condition sustained itself for every tick of the run. It surfaced as 508 ticks and
not one telemetry record emitted, with every gate and null check passing; not as an exception anyone saw.
TryGetSystemMetrics(long, out ReadOnlySpan<SystemTelemetry>)
One tick's per-system telemetry, or false when the ring no longer holds it. The non-throwing peer of GetSystemMetrics(long).
public bool TryGetSystemMetrics(long tickNumber, out ReadOnlySpan<SystemTelemetry> systems)
Parameters
tickNumberlongThe tick to read.
systemsReadOnlySpan<SystemTelemetry>The tick's per-system metrics. Empty when this returns false.
Returns
Remarks
Paired with TryGetTick(long, out TickTelemetry) for the same reason — see its remarks. A walk that guards one and not the other still throws.
TryGetTick(long, out TickTelemetry)
One tick's telemetry, or false when the ring no longer holds it. The non-throwing peer of GetTick(long).
public bool TryGetTick(long tickNumber, out TickTelemetry tick)
Parameters
tickNumberlongThe tick to read.
tickTickTelemetryThe tick's telemetry.
defaultwhen this returns false.
Returns
Remarks
A range from TryGetRange(long, out long, out long) is not a promise that every tick in it still exists when you get there. The clamp is a snapshot and the
validation is live: the tick driver is a concurrent writer, so on a window as wide as Capacity the oldest end clamps EXACTLY to
OldestAvailableTick, and a single tick recorded between resolving the range and reading its first element evicts it. GetTick(long)
then throws — out of whatever public method was reading, which for TyphonRuntime.ReadStats means an operator's stats call fails rather than
returning a slightly short window.
A reader walking a range wants that skipped, not raised: losing the oldest sample of a percentile is the tearing such a reader already accepts by reading a live ring at all, and it is strictly less wrong than no answer. GetTick(long) stays for a caller naming one tick it believes is present, where a throw is the right answer to a bug.