using NLog; using StackExchange.Redis; using System.Globalization; namespace MP.IOC.Services { public class MetricsCalcService : BackgroundService { #region Public Constructors /// /// Metodo x calcolo metriche/statistiche di esecuzone realtime /// /// /// /// public MetricsCalcService(RouteStatsManager stats, IConfiguration config, IConnectionMultiplexer mux) { _stats = stats; _config = config; _db = mux.GetDatabase(); _redisBaseKey = _config.GetValue("ServerConf:RedisBaseKey") ?? "MP_IOC"; } #endregion Public Constructors #region Protected Methods protected override async Task ExecuteAsync(CancellationToken stoppingToken) { var interval = _config.GetValue("RouteMan:MetricCalcIntervalSeconds", 20); while (!stoppingToken.IsCancellationRequested) { try { await Task.Delay(TimeSpan.FromSeconds(interval), stoppingToken); var snapshot = _stats.Snapshot(); if (snapshot.Count == 0) continue; var adesso = DateTime.Now; var hourStart = new DateTime(adesso.Year, adesso.Month, adesso.Day, adesso.Hour, 0, 0); var dayStart = new DateTime(adesso.Year, adesso.Month, adesso.Day, 0, 0, 0); foreach (var kv in snapshot) { string dest = "IO"; string method = "NA"; // verifico se ho chiave completo (dest+method) o parziale string rawKey = kv.Key; if (rawKey.Contains("|")) { var splitVal = rawKey.Split("|"); dest = splitVal[0]; method = splitVal[1]; } else { method = rawKey; } var stat = kv.Value; var count = Interlocked.Read(ref stat.Count); var totalMs = stat.TotalDuration.TotalMilliseconds; var maxMs = stat.MaxDuration.TotalMilliseconds; var minMs = (stat.MinDuration == TimeSpan.MaxValue) ? 0 : stat.MinDuration.TotalMilliseconds; Log.Info($"Dest {dest} | Method {method} | Count {count} | TotalDurationMs {totalMs} | MaxDurationMs {maxMs} | MinDurationMs {minMs}"); if (_db == null) continue; // Keys var hourKey = HourBucketKey(dest, method, hourStart); var hoursIndex = HoursIndexKey(dest, method); var dayKey = DayBucketKey(dest, dayStart); var daysIndex = DaysIndexKey(dest); // Use batch to reduce roundtrips var batch = _db.CreateBatch(); // Increment hash fields (count and totalMs) var taskHourCount = batch.HashIncrementAsync(hourKey, "count", count); var taskHourTotal = batch.HashIncrementAsync(hourKey, "totalMs", totalMs); var taskHourMax = batch.HashSetAsync(hourKey, "maxMs", maxMs.ToString()); var taskHourMin = batch.HashSetAsync(hourKey, "minMs", minMs.ToString()); var taskDayCount = batch.HashIncrementAsync(dayKey, "count", count); var taskDayTotal = batch.HashIncrementAsync(dayKey, "totalMs", totalMs); var taskDayMax = batch.HashSetAsync(dayKey, "maxMs", maxMs.ToString()); var taskDayMin = batch.HashSetAsync(dayKey, "minMs", minMs.ToString()); // Add to sorted set indices with score = epoch seconds of bucket start var hourScore = ToEpochSeconds(hourStart); var dayScore = ToEpochSeconds(dayStart); var taskZAddHour = batch.SortedSetAddAsync(hoursIndex, hourKey, hourScore); var taskZAddDay = batch.SortedSetAddAsync(daysIndex, dayKey, dayScore); // Execute batch batch.Execute(); // Await tasks to ensure completion await Task.WhenAll(taskHourCount, taskHourTotal, taskHourMax, taskHourMin, taskDayCount, taskDayTotal, taskDayMax, taskDayMin, taskZAddHour, taskZAddDay); } _stats.Clear(); } catch (TaskCanceledException) { } catch (Exception ex) { Log.Error(ex, "Error flushing metrics"); } } } #endregion Protected Methods #region Private Fields private static string _redisBaseKey = ""; private static Logger Log = LogManager.GetCurrentClassLogger(); private readonly IConfiguration _config; private readonly IDatabase _db; private readonly RouteStatsManager _stats; #endregion Private Fields #region Private Methods //private static string DayBucketKey(string dest, string method, DateTime dtRif) private static string DayBucketKey(string dest, DateTime dtRif) { return $"{_redisBaseKey}:stats:day:{dest}:{dtRif.ToString("yyyyMMdd", CultureInfo.InvariantCulture)}"; //return $"{_redisBaseKey}:stats:day:{dest}:{method}:{dtRif.ToString("yyyyMMdd", CultureInfo.InvariantCulture)}"; } //private static string DaysIndexKey(string dest, string method) private static string DaysIndexKey(string dest) { return $"{_redisBaseKey}:stats:days:{dest}"; //return $"{_redisBaseKey}:stats:days:{dest}:{method}"; } private static string HourBucketKey(string dest, string method, DateTime dtRif) { return $"{_redisBaseKey}:stats:hour:{dest}:{method}:{dtRif.ToString("yyyyMMddHH", CultureInfo.InvariantCulture)}"; } private static string HoursIndexKey(string dest, string method) { return $"{_redisBaseKey}:stats:hours:{dest}:{method}"; } private static long ToEpochSeconds(DateTime dt) { return new DateTimeOffset(dt).ToUnixTimeSeconds(); } #endregion Private Methods } }