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;
}