using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using Serilog; namespace ZB.MOM.WW.OtOpcUa.Host.Metrics { /// /// Disposable scope returned by . (MXA-008) /// public interface ITimingScope : IDisposable { /// /// Marks whether the timed bridge operation completed successfully. /// /// A value indicating whether the measured operation succeeded. void SetSuccess(bool success); } /// /// Statistics snapshot for a single operation type. /// public class MetricsStatistics { /// /// Gets or sets the total number of recorded executions for the operation. /// public long TotalCount { get; set; } /// /// Gets or sets the number of recorded executions that completed successfully. /// public long SuccessCount { get; set; } /// /// Gets or sets the ratio of successful executions to total executions. /// public double SuccessRate { get; set; } /// /// Gets or sets the mean execution time in milliseconds across the recorded sample. /// public double AverageMilliseconds { get; set; } /// /// Gets or sets the fastest recorded execution time in milliseconds. /// public double MinMilliseconds { get; set; } /// /// Gets or sets the slowest recorded execution time in milliseconds. /// public double MaxMilliseconds { get; set; } /// /// Gets or sets the 95th percentile execution time in milliseconds. /// public double Percentile95Milliseconds { get; set; } } /// /// Per-operation timing and success tracking with a 1000-entry rolling buffer. (MXA-008) /// public class OperationMetrics { private readonly List _durations = new(); private readonly object _lock = new(); private double _maxMilliseconds; private double _minMilliseconds = double.MaxValue; private long _successCount; private long _totalCount; private double _totalMilliseconds; /// /// Records the outcome and duration of a single bridge operation invocation. /// /// The elapsed time for the operation. /// A value indicating whether the operation completed successfully. public void Record(TimeSpan duration, bool success) { lock (_lock) { _totalCount++; if (success) _successCount++; var ms = duration.TotalMilliseconds; _durations.Add(ms); _totalMilliseconds += ms; if (ms < _minMilliseconds) _minMilliseconds = ms; if (ms > _maxMilliseconds) _maxMilliseconds = ms; if (_durations.Count > 1000) _durations.RemoveAt(0); } } /// /// Creates a snapshot of the current statistics for this operation type. /// /// A statistics snapshot suitable for logs, status reporting, and tests. public MetricsStatistics GetStatistics() { lock (_lock) { if (_totalCount == 0) return new MetricsStatistics(); var sorted = _durations.OrderBy(d => d).ToList(); var p95Index = Math.Max(0, (int)Math.Ceiling(sorted.Count * 0.95) - 1); return new MetricsStatistics { TotalCount = _totalCount, SuccessCount = _successCount, SuccessRate = (double)_successCount / _totalCount, AverageMilliseconds = _totalMilliseconds / _totalCount, MinMilliseconds = _minMilliseconds, MaxMilliseconds = _maxMilliseconds, Percentile95Milliseconds = sorted[p95Index] }; } } } /// /// Tracks per-operation performance metrics with periodic logging. (MXA-008) /// public class PerformanceMetrics : IDisposable { private static readonly ILogger Logger = Log.ForContext(); private readonly ConcurrentDictionary _metrics = new(StringComparer.OrdinalIgnoreCase); private readonly Timer _reportingTimer; private bool _disposed; /// /// Initializes a new metrics collector and starts periodic performance reporting. /// public PerformanceMetrics() { _reportingTimer = new Timer(ReportMetrics, null, TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(60)); } /// /// Stops periodic reporting and emits a final metrics snapshot. /// public void Dispose() { if (_disposed) return; _disposed = true; _reportingTimer.Dispose(); ReportMetrics(null); } /// /// Records a completed bridge operation under the specified metrics bucket. /// /// The logical operation name, such as read, write, or subscribe. /// The elapsed time for the operation. /// A value indicating whether the operation completed successfully. public void RecordOperation(string operationName, TimeSpan duration, bool success = true) { var metrics = _metrics.GetOrAdd(operationName, _ => new OperationMetrics()); metrics.Record(duration, success); } /// /// Starts timing a bridge operation and returns a disposable scope that records the result when disposed. /// /// The logical operation name to record. /// A timing scope that reports elapsed time back into this collector. public ITimingScope BeginOperation(string operationName) { return new TimingScope(this, operationName); } /// /// Retrieves the raw metrics bucket for a named operation. /// /// The logical operation name to look up. /// The metrics bucket when present; otherwise, . public OperationMetrics? GetMetrics(string operationName) { return _metrics.TryGetValue(operationName, out var metrics) ? metrics : null; } /// /// Produces a statistics snapshot for all recorded bridge operations. /// /// A dictionary keyed by operation name containing current metrics statistics. public Dictionary GetStatistics() { var result = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var kvp in _metrics) result[kvp.Key] = kvp.Value.GetStatistics(); return result; } private void ReportMetrics(object? state) { foreach (var kvp in _metrics) { var stats = kvp.Value.GetStatistics(); if (stats.TotalCount == 0) continue; Logger.Information( "Metrics: {Operation} — Count={Count}, SuccessRate={SuccessRate:P1}, " + "AvgMs={AverageMs:F1}, MinMs={MinMs:F1}, MaxMs={MaxMs:F1}, P95Ms={P95Ms:F1}", kvp.Key, stats.TotalCount, stats.SuccessRate, stats.AverageMilliseconds, stats.MinMilliseconds, stats.MaxMilliseconds, stats.Percentile95Milliseconds); } } /// /// Timing scope that records one operation result into the owning metrics collector. /// private class TimingScope : ITimingScope { private readonly PerformanceMetrics _metrics; private readonly string _operationName; private readonly Stopwatch _stopwatch; private bool _disposed; private bool _success = true; /// /// Initializes a timing scope for a named bridge operation. /// /// The metrics collector that should receive the result. /// The logical operation name being timed. public TimingScope(PerformanceMetrics metrics, string operationName) { _metrics = metrics; _operationName = operationName; _stopwatch = Stopwatch.StartNew(); } /// /// Marks whether the timed operation should be recorded as successful. /// /// A value indicating whether the operation succeeded. public void SetSuccess(bool success) { _success = success; } /// /// Stops timing and records the operation result once. /// public void Dispose() { if (_disposed) return; _disposed = true; _stopwatch.Stop(); _metrics.RecordOperation(_operationName, _stopwatch.Elapsed, _success); } } } }