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);
}
}
}
}