using System.Collections.Concurrent; namespace MP.IOC.Services { public class RouteStats { public long Count; public TimeSpan TotalDuration = TimeSpan.Zero; public TimeSpan MaxDuration = TimeSpan.Zero; public TimeSpan MinDuration = TimeSpan.MaxValue; public ConcurrentDictionary Destinations = new(); public ConcurrentDictionary StatusCodes = new(); } public class DestinationStats { public long Count; public TimeSpan TotalDuration = TimeSpan.Zero; public TimeSpan MaxDuration = TimeSpan.Zero; public TimeSpan MinDuration = TimeSpan.MaxValue; } public class RouteStatsManager { private readonly ConcurrentDictionary _map = new(); public void Record(string method, string destination) { var stat = _map.GetOrAdd(method, _ => new RouteStats()); Interlocked.Increment(ref stat.Count); var destStat = stat.Destinations.GetOrAdd(destination, _ => new DestinationStats()); Interlocked.Increment(ref destStat.Count); } public void RecordDuration(string method, TimeSpan duration) { if (_map.TryGetValue(method, out var stat)) { lock (stat) { stat.TotalDuration += duration; if (stat.MaxDuration < duration) { stat.MaxDuration = duration; } if (stat.MinDuration > duration) { stat.MinDuration = duration; } foreach (var destStat in stat.Destinations.Values) { lock (destStat) { destStat.TotalDuration += duration; if (destStat.MaxDuration < duration) { destStat.MaxDuration = duration; } if (destStat.MinDuration > duration) { destStat.MinDuration = duration; } } } } } } public void RecordStatusCode(string method, int statusCode) { if (_map.TryGetValue(method, out var stat)) { stat.StatusCodes.AddOrUpdate(statusCode, 1, (_, v) => v + 1); } } public IReadOnlyDictionary Snapshot() { return _map.ToDictionary(kv => kv.Key, kv => kv.Value); } public void Clear() { _map.Clear(); } } }