LmxProxy is no longer needed. Moved the entire lmxproxy/ workspace, DCL adapter files, and related docs to deprecated/. Removed LmxProxy registration from DataConnectionFactory, project reference from DCL, protocol option from UI, and cleaned up all requirement docs.
214 lines
7.2 KiB
C#
214 lines
7.2 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// Provides performance metrics tracking for LmxProxy operations
|
|
/// </summary>
|
|
public class PerformanceMetrics : IDisposable
|
|
{
|
|
private static readonly ILogger Logger = Log.ForContext<PerformanceMetrics>();
|
|
|
|
private readonly ConcurrentDictionary<string, OperationMetrics> _metrics = new();
|
|
private readonly Timer _reportingTimer;
|
|
private bool _disposed;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the PerformanceMetrics class
|
|
/// </summary>
|
|
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
|
|
}
|
|
|
|
/// <summary>
|
|
/// Records the execution time of an operation
|
|
/// </summary>
|
|
public void RecordOperation(string operationName, TimeSpan duration, bool success = true)
|
|
{
|
|
OperationMetrics? metrics = _metrics.GetOrAdd(operationName, _ => new OperationMetrics());
|
|
metrics.Record(duration, success);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a timing scope for measuring operation duration
|
|
/// </summary>
|
|
public ITimingScope BeginOperation(string operationName) => new TimingScope(this, operationName);
|
|
|
|
/// <summary>
|
|
/// Gets current metrics for a specific operation
|
|
/// </summary>
|
|
public OperationMetrics? GetMetrics(string operationName) =>
|
|
_metrics.TryGetValue(operationName, out OperationMetrics? metrics) ? metrics : null;
|
|
|
|
/// <summary>
|
|
/// Gets all current metrics
|
|
/// </summary>
|
|
public IReadOnlyDictionary<string, OperationMetrics> GetAllMetrics() =>
|
|
_metrics.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
|
|
|
/// <summary>
|
|
/// Gets statistics for all operations
|
|
/// </summary>
|
|
public Dictionary<string, MetricsStatistics> GetStatistics() =>
|
|
_metrics.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.GetStatistics());
|
|
|
|
private void ReportMetrics(object? state)
|
|
{
|
|
foreach (KeyValuePair<string, OperationMetrics> 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Timing scope for automatic duration measurement
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Metrics for a specific operation
|
|
/// </summary>
|
|
public class OperationMetrics
|
|
{
|
|
private readonly List<double> _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
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Statistics for an operation
|
|
/// </summary>
|
|
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; }
|
|
}
|
|
}
|