perf(headless): establish K4 resource telemetry

This commit is contained in:
Erik 2026-07-27 10:47:31 +02:00
parent 827a039760
commit cb512fd091
5 changed files with 662 additions and 4 deletions

View file

@ -77,6 +77,158 @@ public sealed class HeadlessProcessSchedulerTests
scheduler.CaptureSnapshot().CatchUpCollapseCount);
}
[Fact]
public void SchedulerMeasuresDeadlineLatenessWithoutAllocatingPerTurn()
{
var time = new ManualTimeProvider();
var operations = new FixtureSessionOperations();
using HeadlessSessionHost first =
CreateSession("late-first", time, operations);
using HeadlessSessionHost second =
CreateSession("late-second", time, operations);
Assert.Equal(
RuntimeSessionStartStatus.Connected,
first.Start().Status);
Assert.Equal(
RuntimeSessionStartStatus.Connected,
second.Start().Status);
var scheduler = new HeadlessProcessScheduler(
[first, second],
time);
time.Advance(TimeSpan.FromMilliseconds(20));
Assert.True(scheduler.DispatchDue(time.GetTimestamp()));
HeadlessSchedulerSnapshot snapshot =
scheduler.CaptureSnapshot();
Assert.Equal(2L, snapshot.LateDeadlineCount);
Assert.Equal(5d, snapshot.MeanLatenessMilliseconds);
Assert.Equal(5d, snapshot.MaximumLatenessMilliseconds);
}
[Fact]
public void ProcessObservationUsesOneAbsoluteDeadlineAndCollapsesPauses()
{
var time = new ManualTimeProvider();
var operations = new FixtureSessionOperations();
using HeadlessSessionHost session =
CreateSession("observation", time, operations);
Assert.Equal(
RuntimeSessionStartStatus.Connected,
session.Start().Status);
int observationCount = 0;
var scheduler = new HeadlessProcessScheduler(
[session],
time,
observation: _ => observationCount++,
observationPeriod: TimeSpan.FromSeconds(1));
time.Advance(TimeSpan.FromMilliseconds(999));
Assert.False(
scheduler.DispatchObservationDue(time.GetTimestamp()));
time.Advance(TimeSpan.FromMilliseconds(1));
Assert.True(
scheduler.DispatchObservationDue(time.GetTimestamp()));
Assert.False(
scheduler.DispatchObservationDue(time.GetTimestamp()));
time.Advance(TimeSpan.FromMilliseconds(2500));
Assert.True(
scheduler.DispatchObservationDue(time.GetTimestamp()));
Assert.False(
scheduler.DispatchObservationDue(time.GetTimestamp()));
Assert.Equal(2, observationCount);
}
[Fact]
public void SessionStartupRebaseExcludesSequentialConnectTime()
{
var time = new ManualTimeProvider();
var operations = new FixtureSessionOperations();
using HeadlessSessionHost first =
CreateSession("rebase-first", time, operations);
using HeadlessSessionHost second =
CreateSession("rebase-second", time, operations);
var scheduler = new HeadlessProcessScheduler(
[first, second],
time);
time.Advance(TimeSpan.FromSeconds(4));
Assert.Equal(
RuntimeSessionStartStatus.Connected,
first.Start().Status);
time.Advance(TimeSpan.FromSeconds(6));
Assert.Equal(
RuntimeSessionStartStatus.Connected,
second.Start().Status);
scheduler.RebaseDeadlinesAfterSessionStart();
Assert.False(scheduler.DispatchDue(time.GetTimestamp()));
time.Advance(TimeSpan.FromMilliseconds(15));
Assert.True(scheduler.DispatchDue(time.GetTimestamp()));
HeadlessSchedulerSnapshot snapshot =
scheduler.CaptureSnapshot();
Assert.Equal(2L, snapshot.TurnCount);
Assert.Equal(0L, snapshot.LateDeadlineCount);
Assert.Equal(0L, snapshot.CatchUpCollapseCount);
Assert.Throws<InvalidOperationException>(
scheduler.RebaseDeadlinesAfterSessionStart);
}
[Theory]
[InlineData(0, 0)]
[InlineData(1, TimeSpan.TicksPerMillisecond)]
[InlineData(
TimeSpan.TicksPerMillisecond - 1,
TimeSpan.TicksPerMillisecond)]
[InlineData(
TimeSpan.TicksPerMillisecond,
TimeSpan.TicksPerMillisecond)]
[InlineData(
TimeSpan.TicksPerMillisecond + 1,
TimeSpan.TicksPerMillisecond + 1)]
public void SchedulerNeverBusyLoopsOnSubMillisecondTimerDelay(
long sourceTicks,
long expectedTicks)
{
TimeSpan normalized =
HeadlessProcessScheduler.NormalizeTimerDelay(
TimeSpan.FromTicks(sourceTicks));
Assert.Equal(expectedTicks, normalized.Ticks);
}
[Fact]
public async Task SystemTimerCadenceDoesNotBusyLoopBetweenTurns()
{
var operations = new FixtureSessionOperations();
using HeadlessSessionHost session =
CreateSession(
"system-timer",
TimeProvider.System,
operations);
Assert.Equal(
RuntimeSessionStartStatus.Connected,
session.Start().Status);
var scheduler = new HeadlessProcessScheduler([session]);
scheduler.RebaseDeadlinesAfterSessionStart();
using var cancellation =
new CancellationTokenSource(TimeSpan.FromMilliseconds(250));
try
{
await scheduler.RunAsync(cancellation.Token);
}
catch (OperationCanceledException)
when (cancellation.IsCancellationRequested)
{
}
HeadlessSchedulerSnapshot snapshot =
scheduler.CaptureSnapshot();
Assert.InRange(snapshot.WaitCount, 1L, 1000L);
Assert.True(snapshot.TurnCount > 0L);
}
[Fact]
public void ReconnectQuiescenceIsAMonotonicDeadlineNotABlockingWait()
{
@ -185,6 +337,25 @@ public sealed class HeadlessProcessSchedulerTests
"password",
diagnostics.ToString(),
StringComparison.Ordinal);
Assert.Contains(
"\"kind\":\"resources\"",
diagnostics.ToString(),
StringComparison.Ordinal);
Assert.Contains(
"\"configuredCount\":2",
diagnostics.ToString(),
StringComparison.Ordinal);
host.Dispose();
Assert.Contains(
"\"state\":\"disposed\"",
diagnostics.ToString(),
StringComparison.Ordinal);
Assert.Contains(
"\"convergedRuntimeCount\":2",
diagnostics.ToString(),
StringComparison.Ordinal);
}
[Fact]