test(loadharness): target-scale load harness for WP-4 / register #25 + row 50
Standalone console harness under tests/ZB.MOM.WW.ScadaBridge.LoadHarness plus a scaled-down Category=Performance smoke [Fact] in PerformanceTests. Deliberately an Exe rather than an xunit suite: the Performance trait enables a filter but does not exclude by default, so a 20-minute test would run on every 'dotnet test' of the slnx. What is real: per-site ActorSystem + LocalDb SQLite file, the real DCL (DataConnectionManagerActor/DataConnectionActor over a SimulatedDataConnection registered through the documented DataConnectionFactory.RegisterAdapter seam), real InstanceActors fed real TagValueUpdates, the real SiteStreamManager, real StreamRelayActor + production-capacity bounded DropOldest channel, real StoreAndForwardService/Storage, real SiteHealthCollector + CentralHealthAggregator. Only the socket hops are stood in for. Measures: end-to-end tag update latency (the emit instant rides TagValueUpdate.Timestamp verbatim to the subscriber), instance ramp, memory growth/CPU over a steady-state window, health report and debug view latency under load, S&F concurrent buffering + drain throughput, and slow-subscriber isolation.
This commit is contained in:
+84
@@ -0,0 +1,84 @@
|
||||
using ZB.MOM.WW.ScadaBridge.LoadHarness;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.PerformanceTests.TargetScale;
|
||||
|
||||
/// <summary>
|
||||
/// Keeps the target-scale load harness (deferred-work register #25 / Phase-8 WP-4)
|
||||
/// honest at CI scale.
|
||||
///
|
||||
/// <para>
|
||||
/// The full protocol — 10 sites x 500 instances x 75 tags sustained for 20 minutes —
|
||||
/// deliberately lives in the standalone <c>ZB.MOM.WW.ScadaBridge.LoadHarness</c>
|
||||
/// executable, NOT here: perf tests in this project run as part of an ordinary
|
||||
/// <c>dotnet test ZB.MOM.WW.ScadaBridge.slnx</c> (the <c>Category=Performance</c>
|
||||
/// trait enables a filter, it does not exclude by default), and a 20-minute test
|
||||
/// would be intolerable there. What this test protects is that the harness still
|
||||
/// compiles, wires up, and produces coherent measurements — so #25's evidence can be
|
||||
/// regenerated on demand rather than bit-rotting.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Results doc: <c>docs/plans/2026-08-15-target-scale-load-test-results.md</c>.
|
||||
/// Design memo: <c>docs/plans/2026-08-15-target-scale-load-test-design.md</c>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public class TargetScaleHarnessSmokeTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Runs the harness at ~1/1000th of target scale for a few seconds and asserts the
|
||||
/// pipeline is intact end to end: tag updates reach live stream subscribers, the
|
||||
/// central health aggregator tracks every site, debug snapshots answer, the
|
||||
/// store-and-forward buffer drains to empty, and a stalled subscriber does not cost
|
||||
/// the healthy ones any events.
|
||||
/// </summary>
|
||||
/// <returns>A task representing the test run.</returns>
|
||||
[Trait("Category", "Performance")]
|
||||
[Fact]
|
||||
public async Task Harness_AtSmokeScale_ProducesCoherentMeasurements()
|
||||
{
|
||||
var config = new HarnessConfig
|
||||
{
|
||||
Sites = 2,
|
||||
InstancesPerSite = 10,
|
||||
TagsPerInstance = 5,
|
||||
TagUpdatePeriod = TimeSpan.FromSeconds(1),
|
||||
SettleDuration = TimeSpan.FromSeconds(5),
|
||||
SustainDuration = TimeSpan.FromSeconds(15),
|
||||
SampleInterval = TimeSpan.FromSeconds(2),
|
||||
// Shortened from the production 30 s only because the smoke window is 20 s.
|
||||
HealthReportInterval = TimeSpan.FromSeconds(2),
|
||||
DebugProbeInterval = TimeSpan.FromSeconds(2),
|
||||
SubscribeSettleDuration = TimeSpan.FromSeconds(5),
|
||||
StreamProbesPerSite = 3,
|
||||
StoreAndForwardDrainMessages = 200,
|
||||
SlowSubscriberEvents = 2_000,
|
||||
ResultsPath = Path.Combine(Path.GetTempPath(), $"loadharness-smoke-{Guid.NewGuid():N}.json"),
|
||||
};
|
||||
|
||||
using var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(10));
|
||||
var result = await HarnessRun.ExecuteAsync(config, _ => { }, cancellation.Token);
|
||||
|
||||
// Tag updates flowed all the way through DCL -> InstanceActor -> site stream ->
|
||||
// StreamRelayActor -> bounded channel -> subscriber.
|
||||
Assert.True(result.TagUpdateLatency.Count > 0,
|
||||
"No tag update latency samples — the DCL -> stream -> subscriber path did not carry traffic.");
|
||||
Assert.True(result.StreamProbeReceived > 0, "Live stream subscribers received nothing.");
|
||||
Assert.Equal(0, result.DriverSkippedNoCallback);
|
||||
|
||||
// Observability held up.
|
||||
Assert.Equal(config.Sites, result.SitesTrackedByAggregator);
|
||||
Assert.True(result.DebugSnapshotsCompleted > 0, "No debug snapshot completed.");
|
||||
Assert.Equal(0, result.DebugSnapshotTimeouts);
|
||||
|
||||
// Store-and-forward drained completely (register row 50, first half).
|
||||
Assert.NotNull(result.StoreAndForwardDrain);
|
||||
Assert.Equal(0, result.StoreAndForwardDrain!.ResidualDepth);
|
||||
Assert.True(result.StoreAndForwardDrain.DrainPerSecond > 0);
|
||||
|
||||
// A stalled subscriber costs the healthy ones nothing (register row 50, second
|
||||
// half). This is the design's isolation claim, asserted rather than assumed.
|
||||
Assert.NotNull(result.SlowSubscriber);
|
||||
Assert.Equal(1.0, result.SlowSubscriber!.HealthyMinDeliveryRatio, precision: 2);
|
||||
Assert.True(result.SlowSubscriber.SlowDeliveryRatio < 1.0,
|
||||
"The deliberately stalled subscriber kept up, so the probe proved nothing.");
|
||||
}
|
||||
}
|
||||
+3
@@ -31,6 +31,9 @@
|
||||
<ProjectReference Include="../../src/ZB.MOM.WW.ScadaBridge.SiteRuntime/ZB.MOM.WW.ScadaBridge.SiteRuntime.csproj" />
|
||||
<!-- Two-node failover rig (TwoNodeClusterFixture) — brings Host + Akka transitively. -->
|
||||
<ProjectReference Include="../../tests/ZB.MOM.WW.ScadaBridge.IntegrationTests/ZB.MOM.WW.ScadaBridge.IntegrationTests.csproj" />
|
||||
<!-- Target-scale load harness (register #25). Referenced so TargetScaleHarnessSmokeTests
|
||||
can run it at CI scale; the full-scale protocol run is the standalone executable. -->
|
||||
<ProjectReference Include="../../tests/ZB.MOM.WW.ScadaBridge.LoadHarness/ZB.MOM.WW.ScadaBridge.LoadHarness.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user