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.LmxProxy.Host.Services { /// /// Provides performance metrics tracking for LmxProxy operations /// public class PerformanceMetrics : IDisposable { private static readonly ILogger Logger = Log.ForContext(); private readonly ConcurrentDictionary _metrics = new(); private readonly Timer _reportingTimer; private bool _disposed; /// /// Initializes a new instance of the PerformanceMetrics class /// public PerformanceMetrics() { // Report metrics every minute _reportingTimer = new Timer(ReportMetrics, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); } public void Dispose() { if (_disposed) { return; } _disposed = true; _reportingTimer?.Dispose(); ReportMetrics(null); // Final report } /// /// Records the execution time of an operation /// public void RecordOperation(string operationName, TimeSpan duration, bool success = true) { OperationMetrics? metrics = _metrics.GetOrAdd(operationName, _ => new OperationMetrics()); metrics.Record(duration, success); } /// /// Creates a timing scope for measuring operation duration /// public ITimingScope BeginOperation(string operationName) => new TimingScope(this, operationName); /// /// Gets current metrics for a specific operation /// public OperationMetrics? GetMetrics(string operationName) => _metrics.TryGetValue(operationName, out OperationMetrics? metrics) ? metrics : null; /// /// Gets all current metrics /// public IReadOnlyDictionary GetAllMetrics() => _metrics.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); /// /// Gets statistics for all operations /// public Dictionary GetStatistics() => _metrics.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.GetStatistics()); private void ReportMetrics(object? state) { foreach (KeyValuePair kvp in _metrics) { MetricsStatistics stats = kvp.Value.GetStatistics(); if (stats.TotalCount > 0) { Logger.Information( "Performance Metrics - {Operation}: Count={Count}, Success={SuccessRate:P}, " + "Avg={AverageMs:F2}ms, Min={MinMs:F2}ms, Max={MaxMs:F2}ms, P95={P95Ms:F2}ms", kvp.Key, stats.TotalCount, stats.SuccessRate, stats.AverageMilliseconds, stats.MinMilliseconds, stats.MaxMilliseconds, stats.Percentile95Milliseconds); } } } /// /// Timing scope for automatic duration measurement /// public interface ITimingScope : IDisposable { void SetSuccess(bool success); } private class TimingScope : ITimingScope { private readonly PerformanceMetrics _metrics; private readonly string _operationName; private readonly Stopwatch _stopwatch; private bool _disposed; private bool _success = true; public TimingScope(PerformanceMetrics metrics, string operationName) { _metrics = metrics; _operationName = operationName; _stopwatch = Stopwatch.StartNew(); } public void SetSuccess(bool success) => _success = success; public void Dispose() { if (_disposed) { return; } _disposed = true; _stopwatch.Stop(); _metrics.RecordOperation(_operationName, _stopwatch.Elapsed, _success); } } } /// /// Metrics for a specific operation /// 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; public void Record(TimeSpan duration, bool success) { lock (_lock) { double ms = duration.TotalMilliseconds; _durations.Add(ms); _totalCount++; if (success) { _successCount++; } _totalMilliseconds += ms; _minMilliseconds = Math.Min(_minMilliseconds, ms); _maxMilliseconds = Math.Max(_maxMilliseconds, ms); // Keep only last 1000 samples for percentile calculation if (_durations.Count > 1000) { _durations.RemoveAt(0); } } } public MetricsStatistics GetStatistics() { lock (_lock) { if (_totalCount == 0) { return new MetricsStatistics(); } var sortedDurations = _durations.OrderBy(d => d).ToList(); int p95Index = (int)Math.Ceiling(sortedDurations.Count * 0.95) - 1; return new MetricsStatistics { TotalCount = _totalCount, SuccessCount = _successCount, SuccessRate = _successCount / (double)_totalCount, AverageMilliseconds = _totalMilliseconds / _totalCount, MinMilliseconds = _minMilliseconds == double.MaxValue ? 0 : _minMilliseconds, MaxMilliseconds = _maxMilliseconds, Percentile95Milliseconds = sortedDurations.Count > 0 ? sortedDurations[Math.Max(0, p95Index)] : 0 }; } } } /// /// Statistics for an operation /// public class MetricsStatistics { public long TotalCount { get; set; } public long SuccessCount { get; set; } public double SuccessRate { get; set; } public double AverageMilliseconds { get; set; } public double MinMilliseconds { get; set; } public double MaxMilliseconds { get; set; } public double Percentile95Milliseconds { get; set; } } }