Three review finds, all in the runtime-toggle path or edge math (the steady-state path was confirmed correct): 1. GPU stale-slot across toggle-off/on: the disabled branch now Disposes the GpuFrameTimer (and nulls it) instead of Stop()-ing and keeping it — a kept instance would poll slots left pending from BEFORE the pause on re-enable and report temporally stale GPU samples. The re-enable branch's existing null guard rebuilds the ring fresh. Dispose is safe there: the branch runs at the top of OnRender with the GL context current. 2. Stale stage accumulation across mid-stage toggle-off: a StageScope disposed after the flag flips still calls EndStage, leaving a partial delta in _stageAccumTicks. The re-baseline branch now Array.Clear()s it alongside the GC re-baselining. 3. FrameStatsBuffer.Max() seeded with 0, clamping all-negative windows (the alloc channel can go negative if the boundary ever crosses threads) and disagreeing with Percentile(). Now seeds from the first live sample after the empty check (slots [0.._count) are always the live window regardless of ring wraparound); empty still returns 0 to match Percentile. TDD: Max_AllNegative_ReturnsTrueMax failed (returned 0) before the fix, passes after. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
59 lines
1.6 KiB
C#
59 lines
1.6 KiB
C#
using AcDream.App.Diagnostics;
|
|
using Xunit;
|
|
|
|
namespace AcDream.App.Tests;
|
|
|
|
public class FrameStatsBufferTests
|
|
{
|
|
[Fact]
|
|
public void Percentiles_OnKnownDistribution_AreExact()
|
|
{
|
|
var buf = new FrameStatsBuffer(capacity: 100);
|
|
// 1..100 µs — p50 = 50, p95 = 95, p99 = 99, max = 100.
|
|
for (long i = 1; i <= 100; i++) buf.Push(i);
|
|
|
|
Assert.Equal(50, buf.Percentile(0.50));
|
|
Assert.Equal(95, buf.Percentile(0.95));
|
|
Assert.Equal(99, buf.Percentile(0.99));
|
|
Assert.Equal(100, buf.Max());
|
|
}
|
|
|
|
[Fact]
|
|
public void Push_PastCapacity_KeepsOnlyNewestWindow()
|
|
{
|
|
var buf = new FrameStatsBuffer(capacity: 4);
|
|
foreach (long v in new long[] { 1000, 1000, 1000, 1000, 1, 2, 3, 4 })
|
|
buf.Push(v);
|
|
// The four 1000s were overwritten; window is {1,2,3,4}.
|
|
Assert.Equal(4, buf.Count);
|
|
Assert.Equal(4, buf.Max());
|
|
Assert.Equal(2, buf.Percentile(0.50));
|
|
}
|
|
|
|
[Fact]
|
|
public void Percentile_Empty_ReturnsZero()
|
|
{
|
|
var buf = new FrameStatsBuffer(capacity: 8);
|
|
Assert.Equal(0, buf.Percentile(0.95));
|
|
Assert.Equal(0, buf.Max());
|
|
Assert.Equal(0, buf.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void Reset_ClearsWindow()
|
|
{
|
|
var buf = new FrameStatsBuffer(capacity: 8);
|
|
buf.Push(5); buf.Push(7);
|
|
buf.Reset();
|
|
Assert.Equal(0, buf.Count);
|
|
Assert.Equal(0, buf.Percentile(0.5));
|
|
}
|
|
|
|
[Fact]
|
|
public void Max_AllNegative_ReturnsTrueMax()
|
|
{
|
|
var buf = new FrameStatsBuffer(capacity: 4);
|
|
buf.Push(-5); buf.Push(-2); buf.Push(-9);
|
|
Assert.Equal(-2, buf.Max());
|
|
}
|
|
}
|