2a5c717755
Bind ContinuousHistorizationOptions (Enabled/OutboxPath/CommitMode/ CommitIntervalMs/DrainBatchSize/DrainIntervalSeconds/Capacity/backoff) with a warn-only Validate(); gated on Enabled AND the ServerHistorian gateway being configured, the Host registers the durable FasterLogHistorizationOutbox (container -disposed) + a gateway-backed GatewayHistorianValueWriter, and binds outbox depth/dropped observable gauges on the central scraped meter. WithOtOpcUaRuntimeActors spawns the recorder (over the same dependency-mux ref) when the options + writer + outbox resolve, registering ContinuousHistorizationRecorderKey. Spawned with an EMPTY historized-ref set: the deployed address space builds later, so ref population is a documented follow-on (a later SetHistorizedRefs feed) — T18 wires the actor + outbox + writer + meters; the ref feed is the known remaining gap. Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
60 lines
3.2 KiB
C#
60 lines
3.2 KiB
C#
using ZB.MOM.WW.OtOpcUa.Commons.Observability;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions.Historian;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Runtime.Historian;
|
|
|
|
/// <summary>
|
|
/// Observable-gauge instruments for the continuous-historization durable outbox, hung off the
|
|
/// central <see cref="OtOpcUaTelemetry.Meter"/> (the same meter the Host's OpenTelemetry /
|
|
/// Prometheus binding already scrapes), so no extra meter allowlist entry is needed.
|
|
/// <para>
|
|
/// The gauges read the bound outbox directly rather than Ask-ing the recorder actor: an
|
|
/// <c>Ask</c> inside a synchronous observable-gauge callback would block the metrics-collection
|
|
/// thread on the actor mailbox. The outbox exposes both gauge sources cheaply and
|
|
/// non-blockingly — <see cref="IHistorizationOutbox.CountAsync"/> completes synchronously (it
|
|
/// just reads an in-memory FIFO count) and <see cref="IHistorizationOutbox.DroppedCount"/> is a
|
|
/// plain property. The recorder's other counters (TotalRecorded / DroppedNonNumeric /
|
|
/// OutboxAppendFailures) remain available via its <c>GetStatus</c> Ask for a health hook, but are
|
|
/// not surfaced as gauges here (Ask-in-gauge is the awkward path the plan calls out).
|
|
/// </para>
|
|
/// </summary>
|
|
public static class ContinuousHistorizationMetrics
|
|
{
|
|
private static volatile IHistorizationOutbox? _outbox;
|
|
|
|
static ContinuousHistorizationMetrics()
|
|
{
|
|
// Registered once on first touch (i.e. when BindOutbox is first called at host start). Instruments
|
|
// are no-op until a listener attaches, so an unbound process pays nothing; the callbacks are
|
|
// null-safe and return 0 until BindOutbox supplies the live outbox.
|
|
OtOpcUaTelemetry.Meter.CreateObservableGauge(
|
|
"otopcua.historization.outbox.depth",
|
|
ObserveDepth,
|
|
unit: "{entry}",
|
|
description: "Un-acked entries currently held in the continuous-historization durable outbox.");
|
|
OtOpcUaTelemetry.Meter.CreateObservableGauge(
|
|
"otopcua.historization.outbox.dropped",
|
|
ObserveDropped,
|
|
unit: "{entry}",
|
|
description: "Lifetime entries the continuous-historization outbox dropped on capacity overflow.");
|
|
}
|
|
|
|
/// <summary>Binds the process outbox the gauges observe. Called once by the Host when continuous
|
|
/// historization is enabled; subsequent calls re-point the gauges (idempotent in practice).</summary>
|
|
/// <param name="outbox">The durable outbox the recorder drains.</param>
|
|
public static void BindOutbox(IHistorizationOutbox outbox)
|
|
=> _outbox = outbox ?? throw new ArgumentNullException(nameof(outbox));
|
|
|
|
private static long ObserveDepth()
|
|
{
|
|
IHistorizationOutbox? outbox = _outbox;
|
|
if (outbox is null) return 0L;
|
|
// CountAsync over the FasterLog outbox completes synchronously (in-memory FIFO count); read the
|
|
// already-completed result without blocking. A (theoretical) pending result reports 0 this scrape.
|
|
ValueTask<int> pending = outbox.CountAsync(CancellationToken.None);
|
|
return pending.IsCompletedSuccessfully ? pending.Result : 0L;
|
|
}
|
|
|
|
private static long ObserveDropped() => _outbox?.DroppedCount ?? 0L;
|
|
}
|