refactor(health): move ISiteHealthCollector to Commons; Communication drops HealthMonitoring reference (ScadaBridge#87)
Interface now lives in Commons.Interfaces.Services; AuditLog no longer gets it transitively through Communication. Implementation stays in HealthMonitoring. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Health;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for site-side health metric collection.
|
||||
/// Consumed by Site Runtime actors to report errors, and by DCL to report connection health.
|
||||
/// </summary>
|
||||
public interface ISiteHealthCollector
|
||||
{
|
||||
/// <summary>
|
||||
/// Increments the script error count.
|
||||
/// </summary>
|
||||
void IncrementScriptError();
|
||||
|
||||
/// <summary>
|
||||
/// Increments the alarm error count.
|
||||
/// </summary>
|
||||
void IncrementAlarmError();
|
||||
|
||||
/// <summary>
|
||||
/// Increments the dead letter count.
|
||||
/// </summary>
|
||||
void IncrementDeadLetter();
|
||||
|
||||
/// <summary>
|
||||
/// Increments the per-interval count of script/alarm runs SHED because
|
||||
/// <c>MaxConcurrentRunsPerScript</c> runs were already in flight for that script. A raw
|
||||
/// per-interval count like the script/alarm error counters — a sustained non-zero value
|
||||
/// means a trigger is firing faster than its script can complete. Every shed is counted
|
||||
/// here even when its accompanying site event is rate-limited away.
|
||||
/// Default interface implementation is a no-op so existing test fakes continue to
|
||||
/// compile without per-fake updates.
|
||||
/// </summary>
|
||||
void IncrementScriptRunShed()
|
||||
{
|
||||
// Default no-op so test fakes do not need to be updated.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increment the per-interval count of
|
||||
/// <c>FallbackAuditWriter</c> primary failures. Bridged from the
|
||||
/// <c>IAuditWriteFailureCounter</c> binding registered via
|
||||
/// <c>AddAuditLogHealthMetricsBridge()</c>.
|
||||
/// </summary>
|
||||
void IncrementSiteAuditWriteFailures();
|
||||
|
||||
/// <summary>
|
||||
/// Increment the per-interval count of
|
||||
/// payload-filter redactor over-redactions (header / body / SQL
|
||||
/// parameter stage throws routed to the
|
||||
/// <c><redacted: redactor error></c> marker). Bridged from the
|
||||
/// <c>IAuditRedactionFailureCounter</c> binding registered via
|
||||
/// <c>AddAuditLogHealthMetricsBridge()</c>.
|
||||
/// </summary>
|
||||
void IncrementAuditRedactionFailure();
|
||||
|
||||
/// <summary>
|
||||
/// Replace the latest site-local
|
||||
/// audit-queue backlog snapshot (pending count, oldest pending row,
|
||||
/// on-disk file bytes) used by the next <see cref="CollectReport"/> call.
|
||||
/// Refreshed periodically by the <c>SiteAuditBacklogReporter</c> hosted
|
||||
/// service so each report carries a recent point-in-time view of the
|
||||
/// site→central drain health.
|
||||
/// </summary>
|
||||
/// <param name="snapshot">The audit backlog snapshot.</param>
|
||||
void UpdateSiteAuditBacklog(SiteAuditBacklogSnapshot snapshot);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the health status for a data connection.
|
||||
/// </summary>
|
||||
/// <param name="connectionName">The name of the connection.</param>
|
||||
/// <param name="health">The connection health status.</param>
|
||||
void UpdateConnectionHealth(string connectionName, ConnectionHealth health);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a connection from health tracking.
|
||||
/// </summary>
|
||||
/// <param name="connectionName">The name of the connection.</param>
|
||||
void RemoveConnection(string connectionName);
|
||||
|
||||
/// <summary>
|
||||
/// Updates tag resolution metrics for a connection.
|
||||
/// </summary>
|
||||
/// <param name="connectionName">The name of the connection.</param>
|
||||
/// <param name="totalSubscribed">Total number of subscribed tags.</param>
|
||||
/// <param name="successfullyResolved">Number of successfully resolved tags.</param>
|
||||
void UpdateTagResolution(string connectionName, int totalSubscribed, int successfullyResolved);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the endpoint for a connection.
|
||||
/// </summary>
|
||||
/// <param name="connectionName">The name of the connection.</param>
|
||||
/// <param name="endpoint">The connection endpoint.</param>
|
||||
void UpdateConnectionEndpoint(string connectionName, string endpoint);
|
||||
|
||||
/// <summary>
|
||||
/// Updates tag quality metrics for a connection.
|
||||
/// </summary>
|
||||
/// <param name="connectionName">The name of the connection.</param>
|
||||
/// <param name="good">Number of good quality tags.</param>
|
||||
/// <param name="bad">Number of bad quality tags.</param>
|
||||
/// <param name="uncertain">Number of uncertain quality tags.</param>
|
||||
void UpdateTagQuality(string connectionName, int good, int bad, int uncertain);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the store-and-forward buffer depths for all categories.
|
||||
/// </summary>
|
||||
/// <param name="depths">Dictionary mapping category names to their buffer depths.</param>
|
||||
void SetStoreAndForwardDepths(IReadOnlyDictionary<string, int> depths);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the counts of instances in each state.
|
||||
/// </summary>
|
||||
/// <param name="deployed">Number of deployed instances.</param>
|
||||
/// <param name="enabled">Number of enabled instances.</param>
|
||||
/// <param name="disabled">Number of disabled instances.</param>
|
||||
void SetInstanceCounts(int deployed, int enabled, int disabled);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the count of parked messages.
|
||||
/// </summary>
|
||||
/// <param name="count">The number of parked messages.</param>
|
||||
void SetParkedMessageCount(int count);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the age (seconds) of the oldest parked store-and-forward message for the
|
||||
/// next <see cref="CollectReport"/> call; <c>null</c> when no rows are parked.
|
||||
/// Point-in-time (not reset on collect), refreshed by the parked-count poller in
|
||||
/// <c>HealthReportSender</c>. Default interface implementation is a no-op so
|
||||
/// existing test fakes continue to compile without per-fake updates.
|
||||
/// </summary>
|
||||
/// <param name="ageSeconds">Age in seconds of the oldest parked row, or <c>null</c> if none are parked.</param>
|
||||
void SetOldestParkedMessageAgeSeconds(double? ageSeconds)
|
||||
{
|
||||
// Default no-op so test fakes do not need to be updated.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace the latest cumulative
|
||||
/// site-event-log write-failure count (SQLite error, disk full,
|
||||
/// bounded-queue overflow drop) used by the next <see cref="CollectReport"/>
|
||||
/// call. Refreshed periodically by the <c>SiteEventLogFailureCountReporter</c>
|
||||
/// hosted service. Point-in-time: the value is NOT reset on
|
||||
/// <see cref="CollectReport"/>; it carries forward until the next poller
|
||||
/// refresh. Default interface implementation is a no-op so existing test
|
||||
/// fakes continue to compile without per-fake updates.
|
||||
/// </summary>
|
||||
/// <param name="count">The cumulative failed-write count from <c>ISiteEventLogger.FailedWriteCount</c>.</param>
|
||||
void SetSiteEventLogWriteFailures(long count)
|
||||
{
|
||||
// Default no-op so test fakes do not need to be updated. The real
|
||||
// SiteHealthCollector overrides this with the Interlocked.Exchange store.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace the latest LocalDb replication status (peer-session connectivity and
|
||||
/// unacked oplog backlog) used by the next <see cref="CollectReport"/> call.
|
||||
/// Refreshed periodically by the <c>LocalDbReplicationStatusReporter</c> hosted
|
||||
/// service. Point-in-time: values are NOT reset on <see cref="CollectReport"/>.
|
||||
/// </summary>
|
||||
/// <param name="connected">
|
||||
/// Whether a sync session is currently running, or <see langword="null"/> when there
|
||||
/// is no reading to give — which is the state of a node with replication UNCONFIGURED
|
||||
/// (register row 80). <see langword="false"/> means "replication is configured on this
|
||||
/// node and the peer session is currently down", i.e. a genuine fault worth a badge;
|
||||
/// reporting that on a deliberately-unreplicated site reads as a fault that isn't one.
|
||||
/// </param>
|
||||
/// <param name="oplogBacklog">
|
||||
/// Unacked oplog entries, or <see langword="null"/> when unknown (failed poll) or not
|
||||
/// applicable (replication unconfigured). Pass null through unchanged: a failed poll
|
||||
/// rendered as 0 would report a broken pair as healthy.
|
||||
/// </param>
|
||||
void SetLocalDbReplicationStatus(bool? connected, long? oplogBacklog)
|
||||
{
|
||||
// Default no-op so test fakes do not need to be updated.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Record whether this node spawned its <c>SiteReconciliationActor</c> at boot, for the
|
||||
/// <c>StartupReconcilerActive</c> field of every subsequent <see cref="CollectReport"/>
|
||||
/// (deferred-work register row 73).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Called ONCE at boot by the Host, which owns the spawn decision — this is a latched
|
||||
/// boot fact, not a polled gauge, so there is no reporter hosted service behind it.
|
||||
/// A collector that is never told (central's self-report, or any test host) leaves the
|
||||
/// field null, which reads as "not reported" rather than "reconciler absent".
|
||||
/// </remarks>
|
||||
/// <param name="active">
|
||||
/// <see langword="true"/> when the reconciler was created; <see langword="false"/> when
|
||||
/// it was skipped because no <c>IDeploymentConfigFetcher</c> was available.
|
||||
/// </param>
|
||||
void SetStartupReconcilerActive(bool active)
|
||||
{
|
||||
// Default no-op so test fakes do not need to be updated.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace the latest per-node deployed-secrets mirror statuses used
|
||||
/// by the <c>SecretMirrorStatuses</c> field of the next <see cref="CollectReport"/> call.
|
||||
/// Refreshed periodically by the site-side <c>SecretMirrorStatusReporter</c> from the
|
||||
/// LocalDb-replicated <c>secret_mirror_status</c> table, so the ACTIVE node's report
|
||||
/// carries the STANDBY node's mirror failures too. Point-in-time: NOT reset on collect.
|
||||
/// </summary>
|
||||
/// <param name="statuses">Every node's row, or <see langword="null"/> for "no data".</param>
|
||||
void SetSecretMirrorStatuses(IReadOnlyList<SecretMirrorNodeStatus>? statuses)
|
||||
{
|
||||
// Default no-op so test fakes do not need to be updated.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace the latest script-execution-scheduler gauges (queue depth, busy
|
||||
/// thread count, and age in seconds of the oldest in-flight script) used by
|
||||
/// the next <see cref="CollectReport"/> call. Refreshed periodically by the
|
||||
/// <c>ScriptSchedulerStatsReporter</c> hosted service. Point-in-time: the
|
||||
/// values are NOT reset on <see cref="CollectReport"/>. S2/UA5 — surfaces a
|
||||
/// saturated or stuck script-execution pool on the site health report.
|
||||
/// Default interface implementation is a no-op so existing test fakes continue
|
||||
/// to compile without per-fake updates.
|
||||
/// </summary>
|
||||
/// <param name="queueDepth">Script tasks waiting to run.</param>
|
||||
/// <param name="busyThreads">Worker threads currently executing a script.</param>
|
||||
/// <param name="oldestBusyAgeSeconds">Age (seconds) of the oldest in-flight script, or <c>null</c> when idle.</param>
|
||||
/// <param name="detachedThreads">
|
||||
/// Worker threads the stuck-script watchdog has detached and replaced because
|
||||
/// their script wedged past its deadline plus grace, and which have not yet returned.
|
||||
/// A non-zero, non-draining value means script bodies are permanently blocking threads.
|
||||
/// </param>
|
||||
void SetScriptSchedulerStats(
|
||||
int queueDepth, int busyThreads, double? oldestBusyAgeSeconds, int detachedThreads = 0)
|
||||
{
|
||||
// Default no-op so test fakes do not need to be updated.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the latest cumulative count of alarm state changes dropped from
|
||||
/// <c>SiteStreamManager</c>'s dedicated alarm publish queue, used by the next
|
||||
/// <see cref="CollectReport"/> call. Refreshed periodically by the site-side
|
||||
/// <c>SiteStreamAlarmDropReporter</c> hosted service. Point-in-time: NOT reset on
|
||||
/// <see cref="CollectReport"/>. Default interface implementation is a no-op so
|
||||
/// existing test fakes continue to compile without per-fake updates.
|
||||
/// </summary>
|
||||
/// <param name="count">The cumulative dropped-alarm count from <c>SiteStreamManager.AlarmPublishDroppedCount</c>.</param>
|
||||
void SetSiteStreamAlarmDropCount(long count)
|
||||
{
|
||||
// Default no-op so test fakes do not need to be updated. The real
|
||||
// SiteHealthCollector overrides this with the Interlocked.Exchange store.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the hostname of this node.
|
||||
/// </summary>
|
||||
/// <param name="hostname">The node hostname.</param>
|
||||
void SetNodeHostname(string hostname);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the list of cluster nodes.
|
||||
/// </summary>
|
||||
/// <param name="nodes">The list of cluster node statuses.</param>
|
||||
void SetClusterNodes(IReadOnlyList<Commons.Messages.Health.NodeStatus> nodes);
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether this node is the active node in the cluster.
|
||||
/// </summary>
|
||||
/// <param name="isActive">True if this node is active, false otherwise.</param>
|
||||
void SetActiveNode(bool isActive);
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether this node is the active node in the cluster.
|
||||
/// </summary>
|
||||
bool IsActiveNode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Collects and returns a health report for a site.
|
||||
/// </summary>
|
||||
/// <param name="siteId">The site identifier.</param>
|
||||
/// <returns>A health report for the specified site.</returns>
|
||||
SiteHealthReport CollectReport(string siteId);
|
||||
|
||||
/// <summary>
|
||||
/// Atomically add back the given per-interval error
|
||||
/// counts into the collector's accumulators. Called by the report sender
|
||||
/// when transport delivery of a freshly-collected report fails, so the
|
||||
/// counts that <see cref="CollectReport"/> already drained roll forward
|
||||
/// into the next report rather than being silently lost. Concurrent
|
||||
/// increments arriving between the failed Send and this restore are
|
||||
/// preserved — <c>Interlocked.Add</c> sums correctly with them. The
|
||||
/// default interface implementation is a no-op so existing test fakes
|
||||
/// (the only implementations outside <see cref="SiteHealthCollector"/>)
|
||||
/// continue to compile without per-fake updates; production callers see
|
||||
/// the real behaviour via the concrete class.
|
||||
/// </summary>
|
||||
/// <param name="scriptErrors">Script error count to add back.</param>
|
||||
/// <param name="alarmErrors">Alarm evaluation error count to add back.</param>
|
||||
/// <param name="deadLetters">Dead letter count to add back.</param>
|
||||
/// <param name="siteAuditWriteFailures">Site audit write failure count to add back.</param>
|
||||
/// <param name="auditRedactionFailures">Audit redaction failure count to add back.</param>
|
||||
/// <param name="scriptRunShedCount">
|
||||
/// Interval-drained script/alarm run-shed count to add back. Additive trailing parameter defaulting to 0 so existing callers keep
|
||||
/// compiling; <c>DetachedScriptThreads</c>/<c>SiteStreamAlarmDropCount</c> are
|
||||
/// point-in-time gauges, not drained on collect, so they need no restore path.
|
||||
/// </param>
|
||||
void AddIntervalCounters(
|
||||
int scriptErrors,
|
||||
int alarmErrors,
|
||||
int deadLetters,
|
||||
int siteAuditWriteFailures,
|
||||
int auditRedactionFailures,
|
||||
int scriptRunShedCount = 0)
|
||||
{
|
||||
// Default no-op so test fakes do not need to be updated. The real
|
||||
// SiteHealthCollector overrides this with the Interlocked.Add restore.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user