From bab8aa71af1dae139ddcd4ec9016f96119a771dc Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Tue, 16 Dec 2025 17:06:50 +0100 Subject: [PATCH] ancora reviiew procedura export verso DB --- .../Services/CalcRuidService.cs | 123 +++++++++++++++++- .../Compo/Stats/HistoricalStats.razor | 2 +- 2 files changed, 122 insertions(+), 3 deletions(-) diff --git a/EgwCoreLib.Lux.Data/Services/CalcRuidService.cs b/EgwCoreLib.Lux.Data/Services/CalcRuidService.cs index 5509dfa8..581b23bf 100644 --- a/EgwCoreLib.Lux.Data/Services/CalcRuidService.cs +++ b/EgwCoreLib.Lux.Data/Services/CalcRuidService.cs @@ -221,7 +221,10 @@ namespace EgwCoreLib.Lux.Data.Services }); // Chiave aggregata per slot orario - var aggHourKey = Key($"stats:agg:{DateTime.UtcNow:yyyyMMddHH}"); + string dayKey = $"stats:agg:{DateTime.UtcNow:yyyyMMddHH}"); + var aggHourKey = Key($"{dayKey}"); + await _db.SetAddAsync("stats:agg:index", dayKey); + // --- Aggiornamento globale (ALL|ALL) --- var globalKey = "ALL|ALL"; @@ -270,6 +273,120 @@ namespace EgwCoreLib.Lux.Data.Services await Task.WhenAll(tasks); } + /// + /// Espostazione statistiche da REDIS al DB + /// + /// + /// + public async Task ExportStatsToDbAsync(bool clearRedisAfterExport = false) + { + var aggregatedData = new List(); + var detailData = new List(); + + // Recupero tutte le chiavi dall'indice (sono RedisValue) + RedisValue[] indexValues = await _db.SetMembersAsync("stats:agg:index"); + + // Ora corrente (slot da saltare) + string currentHourKey = $"stats:agg:{DateTime.UtcNow:yyyyMMddHH}"; + + // Lista chiavi da cancellare dopo export (uso RedisValue perché l'indice è un set di valori) + var keysToDelete = new List(); + + foreach (RedisValue indexValue in indexValues) + { + string keyStr = indexValue.ToString(); + if (keyStr == currentHourKey) continue; // salto slot corrente + + // Estraggo l'ora dalla chiave + string hourStr = keyStr.Replace("stats:agg:", ""); + if (!DateTime.TryParseExact(hourStr, "yyyyMMddHH", null, + System.Globalization.DateTimeStyles.None, out DateTime hour)) + continue; + + // Recupero tutti i valori dell'hash (qui serve RedisKey) + HashEntry[] entries = await _db.HashGetAllAsync((RedisKey)keyStr); + + // --- Globali ALL|ALL --- + RedisValue sumGlobal = entries.FirstOrDefault(e => e.Name == "ALL|ALL:sum").Value; + RedisValue countGlobal = entries.FirstOrDefault(e => e.Name == "ALL|ALL:count").Value; + RedisValue maxGlobal = entries.FirstOrDefault(e => e.Name == "ALL|ALL:max").Value; + + if (!sumGlobal.IsNull && !countGlobal.IsNull) + { + long count = (long)countGlobal; + double sum = (double)sumGlobal; + double max = maxGlobal.IsNull ? 0 : (double)maxGlobal; + + aggregatedData.Add(new StatsAggregated + { + Hour = hour, + RequestCount = count, + AvgDuration = count > 0 ? sum / count : 0, + MaxDuration = max + }); + } + + // --- Dettagli per env|tipo --- + IEnumerable grouped = entries + .Select(e => e.Name.ToString()) + .Where(n => !n.StartsWith("ALL|ALL")) + .Select(n => n.Split(':')[0]) + .Distinct(); + + foreach (string envTipo in grouped) + { + string[] envTipoSplit = envTipo.Split('|'); + if (envTipoSplit.Length != 2) continue; + + string env = envTipoSplit[0]; + string tipo = envTipoSplit[1]; + + RedisValue sumVal = entries.FirstOrDefault(e => e.Name == $"{envTipo}:sum").Value; + RedisValue countVal = entries.FirstOrDefault(e => e.Name == $"{envTipo}:count").Value; + RedisValue maxVal = entries.FirstOrDefault(e => e.Name == $"{envTipo}:max").Value; + + if (!sumVal.IsNull && !countVal.IsNull) + { + long count = (long)countVal; + double sum = (double)sumVal; + double max = maxVal.IsNull ? 0 : (double)maxVal; + + detailData.Add(new StatsDetail + { + Environment = env, + Type = tipo, + Hour = hour, + RequestCount = count, + AvgDuration = count > 0 ? sum / count : 0, + MaxDuration = max + }); + } + } + + // Se richiesto, preparo cancellazione + if (clearRedisAfterExport) + { + keysToDelete.Add(indexValue); // RedisValue coerente + } + } + + // Scrittura su DB + long aggrResult = await dbController.StatsAggrUpsertAsync(aggregatedData, true); + long detailResult = await dbController.StatsDetailUpsertAsync(detailData, true); + + // Se entrambe hanno successo, eseguo batch di cancellazione + if (clearRedisAfterExport && aggrResult > 0 && detailResult > 0 && keysToDelete.Any()) + { + var batch = _db.CreateBatch(); + foreach (RedisValue indexValue in keysToDelete) + { + string keyStr = indexValue.ToString(); + _ = batch.KeyDeleteAsync((RedisKey)keyStr); // cancellazione hash + _ = batch.SetRemoveAsync("stats:agg:index", indexValue); // rimozione dall'indice + } + batch.Execute(); + } + } public async Task> GetOldStatsFromRedisAsync() @@ -319,6 +436,7 @@ namespace EgwCoreLib.Lux.Data.Services return results; } +#if false public async Task AggregateAndSaveStatsAsync(TimeSpan? timeRange = null, bool deleteFromRedis = false) { var now = DateTime.UtcNow; @@ -443,7 +561,8 @@ namespace EgwCoreLib.Lux.Data.Services { Log.Info("Nessun dato da salvare sul DB"); } - } + } +#endif /// /// Metodo Recupero combinazioni envir/tipo diff --git a/Lux.UI/Components/Compo/Stats/HistoricalStats.razor b/Lux.UI/Components/Compo/Stats/HistoricalStats.razor index eec6a263..66fbdc0e 100644 --- a/Lux.UI/Components/Compo/Stats/HistoricalStats.razor +++ b/Lux.UI/Components/Compo/Stats/HistoricalStats.razor @@ -128,7 +128,7 @@ isLoading = true; await InvokeAsync(StateHasChanged); TimeSpan defPeriod = TimeSpan.FromHours(24); - //await Calc.ArchiveOldStatsAsync(defPeriod, false); + await Calc.ExportStatsToDbAsync(false); isLoading = false; }