Merge branch 'grpc-event-batching' — additive site-stream event batching, negotiated, 100ev/25ms window (residual #3 / R2)

This commit is contained in:
Joseph Doherty
2026-08-15 04:13:52 -04:00
16 changed files with 2232 additions and 134 deletions
@@ -93,6 +93,20 @@ public static class ScadaBridgeTelemetry
Meter.CreateCounter<long>("scadabridge.site.stream.events_dropped", unit: "1",
description: "Events evicted from a site gRPC stream's bounded send channel, tagged by stream kind.");
// ---------------- Histograms ----------------
/// <summary>
/// Distribution of how many events each site→central stream frame carried (R2 — gRPC
/// event batching), tagged by stream kind. Recorded ONLY for subscriptions that
/// negotiated batching, so the series' very existence says "this central speaks the
/// batched wire". A distribution pinned at 1 means the coalescing window never sees a
/// backlog (the site is quiet, or the window is too small to be earning anything);
/// mass at the size cap means the cap, not the window, is the binding constraint.
/// </summary>
private static readonly Histogram<int> _siteStreamBatchSize =
Meter.CreateHistogram<int>("scadabridge.site.stream.batch_size", unit: "1",
description: "Events per site gRPC stream frame (1 = unbatched frame), tagged by stream kind.");
// ---------------- Observable gauges ----------------
/// <summary>Current count of open site connections, mutated via <see cref="Interlocked"/>.</summary>
@@ -178,6 +192,16 @@ public static class ScadaBridgeTelemetry
public static void RecordSiteStreamEventDropped(string streamKind) =>
_siteStreamEventDrops.Add(1, new KeyValuePair<string, object?>("stream", streamKind));
/// <summary>
/// Records how many events one site gRPC stream frame carried. Called once per emitted
/// frame on a batching-negotiated subscription (never on an un-negotiated one, where it
/// would degenerate into a per-event instrument on the hottest path in the product).
/// </summary>
/// <param name="streamKind">Stream kind tag (<c>instance</c> or <c>site-alarms</c>).</param>
/// <param name="events">Events packed into the frame; 1 for a plain unbatched frame.</param>
public static void RecordSiteStreamBatchSize(string streamKind, int events) =>
_siteStreamBatchSize.Record(events, new KeyValuePair<string, object?>("stream", streamKind));
/// <summary>
/// Registers the provider the StoreAndForward queue-depth gauge reads on each observation.
/// A later task supplies a provider that reads the real StoreAndForward depth. A null