245db98f5e
Addresses T18 review: GatewayHistorianValueWriter is a DI singleton holding a gRPC channel — make it IAsyncDisposable so the container closes the channel gracefully at shutdown. Tighten the blank-OutboxPath warning to state startup will fail. Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
84 lines
4.9 KiB
C#
84 lines
4.9 KiB
C#
using System.Collections.Generic;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Runtime.Historian;
|
|
|
|
/// <summary>
|
|
/// Binds the <c>ContinuousHistorization</c> configuration section that gates the continuous
|
|
/// historization of driver (non-Galaxy) tag values. When <see cref="Enabled"/> is <c>true</c>
|
|
/// <em>and</em> the <c>ServerHistorian</c> gateway is configured, the Host builds a durable,
|
|
/// crash-safe <c>FasterLogHistorizationOutbox</c> + a gateway-backed <c>IHistorianValueWriter</c>
|
|
/// and <c>WithOtOpcUaRuntimeActors</c> spawns the <see cref="ContinuousHistorizationRecorder"/>;
|
|
/// otherwise no recorder is spawned and driver tag values are not historized.
|
|
/// <para>
|
|
/// The recorder taps the per-node dependency-mux value fan-out, appends each numeric value to
|
|
/// the outbox (the crash boundary), and drains the outbox to the historian's SQL live-value
|
|
/// write path (<c>WriteLiveValues</c>) through the single <c>ServerHistorian</c> gateway. The
|
|
/// gateway connection (endpoint / key / TLS) is sourced from <c>ServerHistorianOptions</c> — this
|
|
/// section carries only the recorder + outbox knobs, never a gateway address or credential.
|
|
/// </para>
|
|
/// </summary>
|
|
public sealed class ContinuousHistorizationOptions
|
|
{
|
|
/// <summary>The configuration section name this options class binds.</summary>
|
|
public const string SectionName = "ContinuousHistorization";
|
|
|
|
/// <summary>
|
|
/// When <c>true</c> (and the <c>ServerHistorian</c> gateway is configured), the
|
|
/// continuous-historization recorder + its durable outbox are wired and spawned; when
|
|
/// <c>false</c> (the default) no recorder is spawned and driver tag values are not historized.
|
|
/// </summary>
|
|
public bool Enabled { get; init; }
|
|
|
|
/// <summary>
|
|
/// Directory holding the FasterLog outbox segment + commit files. Required when
|
|
/// <see cref="Enabled"/> is <c>true</c>. In production set an <b>absolute</b> path on durable
|
|
/// storage — a relative path resolves against the host's working directory, which may change
|
|
/// across deployments.
|
|
/// </summary>
|
|
public string OutboxPath { get; init; } = "";
|
|
|
|
/// <summary>
|
|
/// Outbox commit cadence: <c>PerEntry</c> (the default) fsyncs the log before each append
|
|
/// returns (safest, no loss window); <c>Periodic</c> batches commits onto a background timer
|
|
/// every <see cref="CommitIntervalMs"/> ms (higher throughput, a bounded worst-case loss window).
|
|
/// Parsed case-insensitively against <c>HistorizationCommitMode</c>; an unrecognized value falls
|
|
/// back to <c>PerEntry</c>.
|
|
/// </summary>
|
|
public string CommitMode { get; init; } = "PerEntry";
|
|
|
|
/// <summary>Periodic-mode commit cadence in milliseconds; must be positive when
|
|
/// <see cref="CommitMode"/> is <c>Periodic</c>. Ignored under <c>PerEntry</c>.</summary>
|
|
public int CommitIntervalMs { get; init; } = 100;
|
|
|
|
/// <summary>Maximum outbox entries peeked + written per drain pass; clamped to a positive value by
|
|
/// the recorder (a non-positive value falls back to 64).</summary>
|
|
public int DrainBatchSize { get; init; } = 64;
|
|
|
|
/// <summary>Steady drain cadence in seconds (also the post-success reschedule). Defaults to 2.</summary>
|
|
public double DrainIntervalSeconds { get; init; } = 2;
|
|
|
|
/// <summary>Maximum un-acked outbox entries before the drop-oldest capacity policy kicks in;
|
|
/// <c>0</c> (the default) means unbounded.</summary>
|
|
public int Capacity { get; init; }
|
|
|
|
/// <summary>Initial retry backoff (seconds) after a failed drain pass. Defaults to 1.</summary>
|
|
public double MinBackoffSeconds { get; init; } = 1;
|
|
|
|
/// <summary>Cap (seconds) on the exponential retry backoff after repeated drain failures. Defaults to 30.</summary>
|
|
public double MaxBackoffSeconds { get; init; } = 30;
|
|
|
|
/// <summary>Returns operator-facing misconfiguration warnings for an <c>Enabled</c> recorder
|
|
/// (empty when disabled or correctly configured). Pure — the registration logs each entry.</summary>
|
|
/// <returns>Zero or more human-readable warning messages (never carrying secret values).</returns>
|
|
public IReadOnlyList<string> Validate()
|
|
{
|
|
var warnings = new List<string>();
|
|
if (!Enabled) return warnings;
|
|
if (string.IsNullOrWhiteSpace(OutboxPath))
|
|
warnings.Add("ContinuousHistorization:OutboxPath is empty while historization is enabled — the durable outbox has no directory to persist to; host startup will fail when the outbox is constructed. Set an absolute path on durable storage.");
|
|
if (string.Equals(CommitMode, "Periodic", StringComparison.OrdinalIgnoreCase) && CommitIntervalMs <= 0)
|
|
warnings.Add($"ContinuousHistorization:CommitIntervalMs is {CommitIntervalMs} — must be > 0 in Periodic commit mode; the periodic-commit loop cannot run.");
|
|
return warnings;
|
|
}
|
|
}
|