using System.Collections.Concurrent; using LumaTunnel.Server.Core.Persistence; namespace LumaTunnel.Server.Core.Runtime; public sealed class TrafficCounters { private readonly ConcurrentDictionary _devices = new(StringComparer.Ordinal); private long _activeConnections; public TrafficCounters(IEnumerable? initial = null) { if (initial is null) return; foreach (var record in initial) _devices[record.DeviceId] = new DeviceTrafficCounter(record.UploadBytes, record.DownloadBytes); } public int ActiveConnections => checked((int)Interlocked.Read(ref _activeConnections)); public DeviceTrafficCounter GetDevice(string deviceId) => _devices.GetOrAdd(deviceId, static _ => new DeviceTrafficCounter()); public void ConnectionOpened(string deviceId) { Interlocked.Increment(ref _activeConnections); GetDevice(deviceId).ConnectionOpened(); } public void ConnectionClosed(string deviceId) { Interlocked.Decrement(ref _activeConnections); GetDevice(deviceId).ConnectionClosed(); } public void AddUpload(string deviceId, int bytes) => GetDevice(deviceId).AddUpload(bytes); public void AddDownload(string deviceId, int bytes) => GetDevice(deviceId).AddDownload(bytes); public TrafficDatabase Snapshot() => new() { UpdatedAtUtc = DateTimeOffset.UtcNow, Devices = _devices.OrderBy(static pair => pair.Key).Select(static pair => new TrafficRecord { DeviceId = pair.Key, UploadBytes = pair.Value.UploadBytes, DownloadBytes = pair.Value.DownloadBytes }).ToList() }; } public sealed class DeviceTrafficCounter { private long _activeConnections; private long _uploadBytes; private long _downloadBytes; public DeviceTrafficCounter(long uploadBytes = 0, long downloadBytes = 0) { _uploadBytes = uploadBytes; _downloadBytes = downloadBytes; } public int ActiveConnections => checked((int)Interlocked.Read(ref _activeConnections)); public long UploadBytes => Interlocked.Read(ref _uploadBytes); public long DownloadBytes => Interlocked.Read(ref _downloadBytes); internal void ConnectionOpened() => Interlocked.Increment(ref _activeConnections); internal void ConnectionClosed() => Interlocked.Decrement(ref _activeConnections); internal void AddUpload(int bytes) => Interlocked.Add(ref _uploadBytes, bytes); internal void AddDownload(int bytes) => Interlocked.Add(ref _downloadBytes, bytes); }