513fa1aa16
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
106 lines
4.4 KiB
C#
106 lines
4.4 KiB
C#
using System.Diagnostics;
|
|
using Akka.Actor;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Streaming;
|
|
using ZB.MOM.WW.ScadaBridge.SiteRuntime;
|
|
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Streaming;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.PerformanceTests.Streaming;
|
|
|
|
/// <summary>
|
|
/// Throughput measurement for the site-wide attribute/alarm stream
|
|
/// (<see cref="SiteStreamManager"/>: <c>Source.ActorRef</c> → <c>BroadcastHub</c>
|
|
/// with per-subscriber buffering, <c>DropHead</c> overflow). Design envelope: this
|
|
/// stream carries every attribute value change on a site; it must comfortably
|
|
/// sustain tens of thousands of events/sec. Thresholds are deliberately
|
|
/// conservative (CI-machine safe) — this test exists to catch order-of-magnitude
|
|
/// regressions, not to benchmark.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// This is the first of the two highest-value real measurements arch-review 08 §2.3
|
|
/// called for. It follows the existing <c>HotPathLatencyTests</c> style:
|
|
/// <see cref="Stopwatch"/>-based, <c>[Trait("Category", "Performance")]</c>,
|
|
/// conservative thresholds so CI stays green on slow machines.
|
|
/// </para>
|
|
/// <para>
|
|
/// It spins up a real (non-clustered) <see cref="ActorSystem"/> rather than using
|
|
/// Akka.TestKit — the PerformanceTests project references SiteRuntime (which brings
|
|
/// in Akka + Akka.Streams transitively) but not Akka.TestKit.Xunit2, so no csproj
|
|
/// change is required.
|
|
/// </para>
|
|
/// </remarks>
|
|
public class SiteStreamThroughputTests
|
|
{
|
|
private sealed class CountingActor : ReceiveActor
|
|
{
|
|
public static long Received;
|
|
|
|
/// <summary>Initializes the actor and registers the handlers that count received attribute value changes.</summary>
|
|
public CountingActor()
|
|
{
|
|
// The manager Tells the raw ISiteStreamEvent record (AttributeValueChanged)
|
|
// straight to the subscriber — no wrapper — so we match on the record.
|
|
Receive<AttributeValueChanged>(_ => Interlocked.Increment(ref Received));
|
|
ReceiveAny(_ => { });
|
|
}
|
|
}
|
|
|
|
/// <summary>Verifies that the site stream sustains at least 10,000 events per second to a single subscriber.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Trait("Category", "Performance")]
|
|
[Fact]
|
|
public async Task SiteStream_SustainsAtLeast10kEventsPerSecond_ToOneSubscriber()
|
|
{
|
|
const int eventCount = 100_000;
|
|
CountingActor.Received = 0;
|
|
|
|
var system = ActorSystem.Create("SiteStreamThroughputTests");
|
|
try
|
|
{
|
|
// Buffer sized to the whole burst so DropHead should not fire.
|
|
var manager = new SiteStreamManager(
|
|
new SiteRuntimeOptions { StreamBufferSize = eventCount },
|
|
NullLogger<SiteStreamManager>.Instance);
|
|
manager.Initialize(system);
|
|
|
|
var counter = system.ActorOf(Props.Create<CountingActor>());
|
|
manager.Subscribe("PerfInstance", counter);
|
|
|
|
var sw = Stopwatch.StartNew();
|
|
for (var i = 0; i < eventCount; i++)
|
|
{
|
|
manager.PublishAttributeValueChanged(new AttributeValueChanged(
|
|
"PerfInstance", "Attr", "Attr", i, "Good", DateTimeOffset.UtcNow));
|
|
}
|
|
|
|
// Drain: wait until the counter goes quiet (no growth across a 200ms window).
|
|
long last = -1;
|
|
var deadline = DateTime.UtcNow.AddSeconds(30);
|
|
while (DateTime.UtcNow < deadline)
|
|
{
|
|
var now = Interlocked.Read(ref CountingActor.Received);
|
|
if (now == last && now > 0) break;
|
|
last = now;
|
|
await Task.Delay(200);
|
|
}
|
|
sw.Stop();
|
|
|
|
var received = Interlocked.Read(ref CountingActor.Received);
|
|
var eventsPerSecond = received / sw.Elapsed.TotalSeconds;
|
|
|
|
// DropHead means under extreme pressure some events may drop; with the
|
|
// buffer sized to the burst, deliver ratio should be ~1.0.
|
|
Assert.True(received >= eventCount * 0.9,
|
|
$"Expected >=90% delivery, got {received}/{eventCount}");
|
|
Assert.True(eventsPerSecond >= 10_000,
|
|
$"Expected >=10k events/s sustained, got {eventsPerSecond:F0}/s over {sw.Elapsed.TotalSeconds:F1}s " +
|
|
$"(delivered {received}/{eventCount})");
|
|
}
|
|
finally
|
|
{
|
|
await system.Terminate();
|
|
}
|
|
}
|
|
}
|