using System.Collections.Concurrent; namespace ZB.MOM.WW.LmxProxy.Client; public partial class LmxProxyClient { /// /// Tracks per-operation counts, errors, and latency with rolling buffer and percentile support. /// internal class ClientMetrics { private readonly ConcurrentDictionary _operationCounts = new(); private readonly ConcurrentDictionary _errorCounts = new(); private readonly ConcurrentDictionary> _latencies = new(); private readonly Lock _latencyLock = new(); public void IncrementOperationCount(string operation) { _operationCounts.AddOrUpdate(operation, 1, (_, count) => count + 1); } public void IncrementErrorCount(string operation) { _errorCounts.AddOrUpdate(operation, 1, (_, count) => count + 1); } public void RecordLatency(string operation, long milliseconds) { lock (_latencyLock) { if (!_latencies.TryGetValue(operation, out var list)) { list = []; _latencies[operation] = list; } list.Add(milliseconds); if (list.Count > 1000) { list.RemoveAt(0); } } } public Dictionary GetSnapshot() { var snapshot = new Dictionary(); foreach (var kvp in _operationCounts) { snapshot[$"{kvp.Key}_count"] = kvp.Value; } foreach (var kvp in _errorCounts) { snapshot[$"{kvp.Key}_errors"] = kvp.Value; } lock (_latencyLock) { foreach (var kvp in _latencies) { var values = kvp.Value; if (values.Count == 0) continue; double avg = values.Average(); snapshot[$"{kvp.Key}_avg_latency_ms"] = Math.Round(avg, 2); snapshot[$"{kvp.Key}_p95_latency_ms"] = GetPercentile(values, 95); snapshot[$"{kvp.Key}_p99_latency_ms"] = GetPercentile(values, 99); } } return snapshot; } private static long GetPercentile(List values, int percentile) { var sorted = values.OrderBy(v => v).ToList(); int index = Math.Max(0, (int)Math.Ceiling(percentile / 100.0 * sorted.Count) - 1); return sorted[index]; } } }