94 lines
4.2 KiB
C#
94 lines
4.2 KiB
C#
using System.Diagnostics;
|
||
using Shouldly;
|
||
using Xunit;
|
||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||
using ZB.MOM.WW.OtOpcUa.Driver.TwinCAT;
|
||
|
||
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests;
|
||
|
||
/// <summary>
|
||
/// PR 2.1 perf gate — verifies the bulk Sum-read path is materially faster than the
|
||
/// equivalent serial per-tag <see cref="TwinCAT.Ads.AdsClient.ReadValueAsync(string, Type, System.Threading.CancellationToken)"/>
|
||
/// loop on a real XAR runtime. The driver's <see cref="TwinCATDriver.ReadAsync"/>
|
||
/// already routes through the bulk path; the baseline calls through a hand-rolled
|
||
/// loop so we're comparing apples to apples on the same wire.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// Requires the perf fixture: <c>GVL_Perf.aTags : ARRAY[1..1000] OF DINT</c> per
|
||
/// <c>TwinCatProject/README.md §Performance scenarios</c>. Skipped unless
|
||
/// <c>TWINCAT_PERF=1</c> is set.
|
||
/// </remarks>
|
||
[Collection("TwinCATXar")]
|
||
[Trait("Category", "Performance")]
|
||
[Trait("Simulator", "TwinCAT-XAR")]
|
||
public sealed class TwinCATSumCommandPerfTests(TwinCATXarFixture sim)
|
||
{
|
||
private const int TagCount = 1000;
|
||
|
||
[TwinCATPerfFact]
|
||
public async Task Driver_sum_read_1000_tags_beats_loop_baseline_by_5x()
|
||
{
|
||
if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason);
|
||
|
||
var deviceAddress = $"ads://{sim.TargetNetId}:{sim.AmsPort}";
|
||
var tags = new TwinCATTagDefinition[TagCount];
|
||
var refs = new string[TagCount];
|
||
for (var i = 0; i < TagCount; i++)
|
||
{
|
||
// GVL_Perf.aTags is 1-based per IEC 61131-3 ARRAY declaration.
|
||
var name = $"Perf{i + 1}";
|
||
refs[i] = name;
|
||
tags[i] = new TwinCATTagDefinition(
|
||
Name: name,
|
||
DeviceHostAddress: deviceAddress,
|
||
SymbolPath: $"GVL_Perf.aTags[{i + 1}]",
|
||
DataType: TwinCATDataType.DInt);
|
||
}
|
||
|
||
var options = new TwinCATDriverOptions
|
||
{
|
||
Devices = [new TwinCATDeviceOptions(deviceAddress, "XAR-VM")],
|
||
Tags = tags,
|
||
UseNativeNotifications = false,
|
||
Timeout = TimeSpan.FromSeconds(15),
|
||
Probe = new TwinCATProbeOptions { Enabled = false },
|
||
};
|
||
|
||
// ---- Bulk (PR 2.1) measurement ----
|
||
await using var bulkDrv = new TwinCATDriver(options, "tc3-perf-bulk");
|
||
await bulkDrv.InitializeAsync("{}", TestContext.Current.CancellationToken);
|
||
|
||
// Warm-up: prime symbol-handle caches in the XAR runtime so the timed run
|
||
// measures sum-read steady state, not first-touch handle resolution.
|
||
await bulkDrv.ReadAsync(refs, TestContext.Current.CancellationToken);
|
||
|
||
var bulkSw = Stopwatch.StartNew();
|
||
var bulkResults = await bulkDrv.ReadAsync(refs, TestContext.Current.CancellationToken);
|
||
bulkSw.Stop();
|
||
|
||
// ---- Loop baseline measurement ----
|
||
// Use a fresh driver instance so the cache state matches the bulk run on first
|
||
// call. Single-tag-per-call across the full set is what PR 2.1 replaces.
|
||
await using var loopDrv = new TwinCATDriver(options, "tc3-perf-loop");
|
||
await loopDrv.InitializeAsync("{}", TestContext.Current.CancellationToken);
|
||
// Warm-up against the same refs.
|
||
for (var i = 0; i < TagCount; i++)
|
||
_ = await loopDrv.ReadAsync([refs[i]], TestContext.Current.CancellationToken);
|
||
|
||
var loopSw = Stopwatch.StartNew();
|
||
for (var i = 0; i < TagCount; i++)
|
||
_ = await loopDrv.ReadAsync([refs[i]], TestContext.Current.CancellationToken);
|
||
loopSw.Stop();
|
||
|
||
// Sanity: bulk path must produce TagCount snapshots all Good.
|
||
bulkResults.Count.ShouldBe(TagCount);
|
||
bulkResults.ShouldAllBe(s => s.StatusCode == TwinCATStatusMapper.Good);
|
||
|
||
// Conservative ratio — on the dev box bulk is ~10-20× the loop, target is 5×.
|
||
// Lower bound exists so the test is robust to noisy CI / VM scheduling.
|
||
var ratio = (double)loopSw.ElapsedMilliseconds / Math.Max(1, bulkSw.ElapsedMilliseconds);
|
||
ratio.ShouldBeGreaterThan(5.0,
|
||
$"Bulk Sum-read should be >5x faster than per-tag loop; got bulk={bulkSw.ElapsedMilliseconds}ms, loop={loopSw.ElapsedMilliseconds}ms (ratio={ratio:F2})");
|
||
}
|
||
}
|