c2bdb7d5de
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
59 lines
2.8 KiB
C#
59 lines
2.8 KiB
C#
namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
|
|
/// <summary>
|
|
/// Optional driver capability: expose a small bag of operator-facing diagnostic key/value pairs
|
|
/// (e.g. S7's negotiated PDU size) that ride the driver-health snapshot to the AdminUI <c>/hosts</c>
|
|
/// page (Gitea #640).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// <b>Why a bag rather than fields on <see cref="DriverHealth"/>.</b> The facts worth showing are
|
|
/// driver-specific and do not change the driver's <i>state</i>; a typed field per driver would
|
|
/// widen the core record and the telemetry proto for every driver. Same additive-never-a-state-change
|
|
/// rule as <see cref="ISkippedTagReporter"/>.
|
|
/// </para>
|
|
/// <para>
|
|
/// Values are strings so the telemetry proto stays a plain <c>map<string,string></c>. Keep the
|
|
/// bag small (<see cref="DriverDiagnostics.MaxEntries"/> entries, text truncated to
|
|
/// <see cref="DriverDiagnostics.MaxValueLength"/>): it is published on every state change.
|
|
/// </para>
|
|
/// </remarks>
|
|
public interface IDriverDiagnostics
|
|
{
|
|
/// <summary>
|
|
/// Snapshot of the driver's diagnostics. Must be a pure in-memory read with no I/O — it is called on
|
|
/// the health-publish path, deliberately NOT through the resilience pipeline.
|
|
/// </summary>
|
|
/// <returns>The key/value pairs; empty when there is nothing to show yet.</returns>
|
|
IReadOnlyDictionary<string, string> GetDiagnostics();
|
|
}
|
|
|
|
/// <summary>Bounds and helpers shared by every consumer of <see cref="IDriverDiagnostics"/>.</summary>
|
|
public static class DriverDiagnostics
|
|
{
|
|
/// <summary>Maximum number of entries carried on a health snapshot; extras are dropped (ordinal key order).</summary>
|
|
public const int MaxEntries = 16;
|
|
|
|
/// <summary>Maximum length of a key or value carried on a health snapshot; longer text is truncated.</summary>
|
|
public const int MaxValueLength = 200;
|
|
|
|
/// <summary>
|
|
/// Normalises a driver's bag for publishing: ordinal key order, capped entry count, truncated text.
|
|
/// Deterministic so the same facts always produce the same fingerprint digest.
|
|
/// </summary>
|
|
/// <param name="raw">The bag the driver returned.</param>
|
|
/// <returns>A bounded, ordered copy.</returns>
|
|
public static IReadOnlyDictionary<string, string> Bound(IReadOnlyDictionary<string, string> raw)
|
|
{
|
|
var result = new SortedDictionary<string, string>(StringComparer.Ordinal);
|
|
foreach (var kv in raw.OrderBy(k => k.Key, StringComparer.Ordinal))
|
|
{
|
|
if (result.Count >= MaxEntries) break;
|
|
result[Truncate(kv.Key ?? "")] = Truncate(kv.Value ?? "");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private static string Truncate(string s) => s.Length <= MaxValueLength ? s : s[..MaxValueLength];
|
|
}
|