deprecate(lmxproxy): move all LmxProxy code, tests, and docs to deprecated/

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.
This commit is contained in:
Joseph Doherty
2026-04-08 15:56:23 -04:00
parent 8423915ba1
commit 9dccf8e72f
220 changed files with 25 additions and 132 deletions

View File

@@ -0,0 +1,213 @@
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; }
}
}