feat(hosts): driver diagnostics channel on /hosts, S7 negotiated PDU size first (#640)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -1153,6 +1153,28 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Bounded driver diagnostics, or null when the driver is not an <see cref="IDriverDiagnostics"/>
|
||||
/// (Gitea #640). Pulled fresh and called UNGUARDED for the same reasons as
|
||||
/// <see cref="CurrentSkippedTags"/>; a throwing implementation loses only the diagnostics.</summary>
|
||||
private IReadOnlyDictionary<string, string>? CurrentDiagnostics()
|
||||
{
|
||||
if (_driver is not IDriverDiagnostics diag) return null;
|
||||
try
|
||||
{
|
||||
return DriverDiagnostics.Bound(diag.GetDiagnostics());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warning(ex, "DriverInstance {Id}: GetDiagnostics threw during health publish; omitting diagnostics", _driverInstanceId);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Value digest of the diagnostics bag for the publish fingerprint. Null in, null out. The bag is
|
||||
/// already in ordinal key order (<see cref="DriverDiagnostics.Bound"/>), so equal facts digest equally.</summary>
|
||||
private static string? DiagnosticsDigest(IReadOnlyDictionary<string, string>? d) =>
|
||||
d is null ? null : string.Join('|', d.Select(kv => $"{kv.Key}={kv.Value}"));
|
||||
|
||||
/// <summary>Records the driver's rediscovery raise and re-publishes health so the signal reaches the
|
||||
/// AdminUI promptly rather than waiting for the next 30 s heartbeat.
|
||||
/// <para><b>Advisory only.</b> The served address space is deliberately NOT rebuilt: v3 authors raw tags
|
||||
@@ -1357,6 +1379,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
|
||||
var errorCount = ErrorCount5Min();
|
||||
var hostStatuses = CurrentHostStatuses();
|
||||
var skippedTags = CurrentSkippedTags();
|
||||
var diagnostics = CurrentDiagnostics();
|
||||
// _rediscoveryNeededUtc is PART OF THE FINGERPRINT on purpose. A rediscovery raise on an
|
||||
// otherwise-unchanged Healthy driver leaves (state, lastSuccess, lastError, errorCount)
|
||||
// identical, so without it the dedup below would swallow the very publish that carries the
|
||||
@@ -1375,14 +1398,20 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
|
||||
// is identical to a clean driver's and the publish that carries the signal is exactly the one
|
||||
// dedup would swallow. Flattened to a STRING for the same reason as the host digest — the
|
||||
// report holds an IReadOnlyList, which a tuple compares by reference.
|
||||
//
|
||||
// The diagnostics digest (Gitea #640) is the fourth instance: a diagnostic fact (S7's PDU size
|
||||
// changing across a reconnect) moves none of the other components, so without it the publish
|
||||
// that carries it is deduped away. Flattened to a STRING - the bag is a dictionary, which a
|
||||
// tuple compares by reference.
|
||||
var fingerprint = (health.State, health.LastSuccessfulRead, health.LastError, errorCount,
|
||||
_rediscoveryNeededUtc, HostStatusDigest(hostStatuses), SkippedTagDigest(skippedTags));
|
||||
_rediscoveryNeededUtc, HostStatusDigest(hostStatuses), SkippedTagDigest(skippedTags),
|
||||
DiagnosticsDigest(diagnostics));
|
||||
if (_lastPublishedFingerprint is { } prev && prev.Equals(fingerprint))
|
||||
return observed;
|
||||
_lastPublishedFingerprint = fingerprint;
|
||||
_healthPublisher.Publish(
|
||||
_clusterId, _driverInstanceId, health, errorCount, _rediscoveryNeededUtc, _rediscoveryReason,
|
||||
hostStatuses, skippedTags);
|
||||
hostStatuses, skippedTags, diagnostics);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1418,7 +1447,8 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
|
||||
|
||||
/// <summary>Fingerprint of the last <see cref="PublishHealthSnapshot"/> call; null until first publish.</summary>
|
||||
private (DriverState State, DateTime? LastSuccess, string? LastError, int ErrorCount,
|
||||
DateTime? RediscoveryNeededUtc, string? HostStatusDigest, string? SkippedTagDigest)? _lastPublishedFingerprint;
|
||||
DateTime? RediscoveryNeededUtc, string? HostStatusDigest, string? SkippedTagDigest,
|
||||
string? DiagnosticsDigest)? _lastPublishedFingerprint;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void PostStop()
|
||||
|
||||
Reference in New Issue
Block a user