feat(telemetry): export the site script compile-cache eviction counter as a Meter (ScadaBridge#68)

SiteScriptCompileCache now records each eviction sweep on scadabridge.site.script_compile_cache.evictions
(ScadaBridgeTelemetry). It rides the existing ZB.MOM.WW.ScadaBridge meter, which is already in
SiteServiceRegistration.ObservedMeters, so no new allowlist entry is needed; a Host test pins that
the meter stays allowlisted.
This commit is contained in:
Joseph Doherty
2026-11-09 12:08:25 -05:00
parent e2b15bd520
commit 79957c4b96
4 changed files with 59 additions and 0 deletions
@@ -245,6 +245,17 @@ public class SiteLocalDbWiringTests : IDisposable
Assert.Contains(LocalDbMetrics.MeterName, SiteServiceRegistration.ObservedMeters);
}
/// <summary>Verifies that the ScadaBridge application meter (carrying the compile-cache eviction counter) is exported.</summary>
[Fact]
public void Site_Telemetry_Allowlists_TheScadaBridgeMeter()
{
// ScadaBridge#68: scadabridge.site.script_compile_cache.evictions lives on this meter; an
// unlisted meter is dropped silently (ZbTelemetryOptions.Meters is an allowlist).
Assert.Contains(
ZB.MOM.WW.ScadaBridge.Commons.Observability.ScadaBridgeTelemetry.MeterName,
SiteServiceRegistration.ObservedMeters);
}
/// <summary>Verifies that the Phase 2 tables are created in the consolidated LocalDb file.</summary>
[Fact]
public void Site_LocalDb_CreatesThePhase2Tables_InTheConsolidatedFile()
@@ -253,4 +253,32 @@ public class SiteScriptCompileCacheTests
Assert.True(cache.Count <= 16);
Assert.True(cache.Evictions > 0);
}
/// <summary>
/// ScadaBridge#68: evictions are exported as a Meter counter (not only the rate-limited Warning
/// log), on the ScadaBridge meter that the Host already allowlists for export.
/// </summary>
[Fact]
public void Eviction_IsExportedAsAMeterCounter()
{
long exported = 0;
using var listener = new System.Diagnostics.Metrics.MeterListener();
listener.InstrumentPublished = (instrument, l) =>
{
if (instrument.Meter.Name == ZB.MOM.WW.ScadaBridge.Commons.Observability.ScadaBridgeTelemetry.MeterName
&& instrument.Name == "scadabridge.site.script_compile_cache.evictions")
l.EnableMeasurementEvents(instrument);
};
listener.SetMeasurementEventCallback<long>((_, measurement, _, _) => Interlocked.Add(ref exported, measurement));
listener.Start();
var cache = new SiteScriptCompileCache(16, logger: null);
for (var i = 0; i < 40; i++)
cache.GetOrAdd("return " + i + ";", typeof(ScriptGlobals), Ok);
// The instrument is process-wide, so a parallel test's evictions may add to it: bound from below.
Assert.True(cache.Evictions > 0);
Assert.True(Interlocked.Read(ref exported) >= cache.Evictions,
$"exported {exported} but the cache evicted {cache.Evictions}");
}
}