using System.Collections.Concurrent; namespace ZB.MOM.WW.OtOpcUa.Core.Resilience; /// /// Process-singleton tracker of live resilience counters per /// (DriverInstanceId, HostName). Populated by the CapabilityInvoker and the /// MemoryTracking layer; consumed by a HostedService that periodically persists a /// snapshot to the DriverInstanceResilienceStatus table for Admin /hosts. /// /// /// Per Phase 6.1 Stream E. No DB dependency here — the tracker is pure in-memory so /// tests can exercise it without EF Core or SQL Server. The HostedService that writes /// snapshots lives in the Server project (Stream E.2); the actual SignalR push + Blazor /// page refresh (E.3) lands in a follow-up visual-review PR. /// public sealed class DriverResilienceStatusTracker { private readonly ConcurrentDictionary _status = new(); /// Record a Polly pipeline failure for . public void RecordFailure(string driverInstanceId, string hostName, DateTime utcNow) { var key = new StatusKey(driverInstanceId, hostName); _status.AddOrUpdate(key, _ => new ResilienceStatusSnapshot { ConsecutiveFailures = 1, LastSampledUtc = utcNow }, (_, existing) => existing with { ConsecutiveFailures = existing.ConsecutiveFailures + 1, LastSampledUtc = utcNow, }); } /// Reset the consecutive-failure count on a successful pipeline execution. public void RecordSuccess(string driverInstanceId, string hostName, DateTime utcNow) { var key = new StatusKey(driverInstanceId, hostName); _status.AddOrUpdate(key, _ => new ResilienceStatusSnapshot { ConsecutiveFailures = 0, LastSampledUtc = utcNow }, (_, existing) => existing with { ConsecutiveFailures = 0, LastSampledUtc = utcNow, }); } /// Record a circuit-breaker open event. public void RecordBreakerOpen(string driverInstanceId, string hostName, DateTime utcNow) { var key = new StatusKey(driverInstanceId, hostName); _status.AddOrUpdate(key, _ => new ResilienceStatusSnapshot { LastBreakerOpenUtc = utcNow, LastSampledUtc = utcNow }, (_, existing) => existing with { LastBreakerOpenUtc = utcNow, LastSampledUtc = utcNow }); } /// Record a process recycle event (Tier C only). public void RecordRecycle(string driverInstanceId, string hostName, DateTime utcNow) { var key = new StatusKey(driverInstanceId, hostName); _status.AddOrUpdate(key, _ => new ResilienceStatusSnapshot { LastRecycleUtc = utcNow, LastSampledUtc = utcNow }, (_, existing) => existing with { LastRecycleUtc = utcNow, LastSampledUtc = utcNow }); } /// Capture / update the MemoryTracking-supplied baseline + current footprint. public void RecordFootprint(string driverInstanceId, string hostName, long baselineBytes, long currentBytes, DateTime utcNow) { var key = new StatusKey(driverInstanceId, hostName); _status.AddOrUpdate(key, _ => new ResilienceStatusSnapshot { BaselineFootprintBytes = baselineBytes, CurrentFootprintBytes = currentBytes, LastSampledUtc = utcNow, }, (_, existing) => existing with { BaselineFootprintBytes = baselineBytes, CurrentFootprintBytes = currentBytes, LastSampledUtc = utcNow, }); } /// Snapshot of a specific (instance, host) pair; null if no counters recorded yet. public ResilienceStatusSnapshot? TryGet(string driverInstanceId, string hostName) => _status.TryGetValue(new StatusKey(driverInstanceId, hostName), out var snapshot) ? snapshot : null; /// Copy of every currently-tracked (instance, host, snapshot) triple. Safe under concurrent writes. public IReadOnlyList<(string DriverInstanceId, string HostName, ResilienceStatusSnapshot Snapshot)> Snapshot() => _status.Select(kvp => (kvp.Key.DriverInstanceId, kvp.Key.HostName, kvp.Value)).ToList(); private readonly record struct StatusKey(string DriverInstanceId, string HostName); } /// Snapshot of the resilience counters for one (DriverInstanceId, HostName) pair. public sealed record ResilienceStatusSnapshot { public int ConsecutiveFailures { get; init; } public DateTime? LastBreakerOpenUtc { get; init; } public DateTime? LastRecycleUtc { get; init; } public long BaselineFootprintBytes { get; init; } public long CurrentFootprintBytes { get; init; } public DateTime LastSampledUtc { get; init; } }