From 9f22e152e74796ed882c1e83a272895de3a6a55a Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Wed, 29 Apr 2026 10:49:00 +0200 Subject: [PATCH] Aggiunta gestione dati callStat persistenti su Redis --- IOB-UT-NEXT/CallMetricsCollector.cs | 83 ++++++++++++++++++- IOB-UT-NEXT/RedisMan.cs | 6 +- IOB-UT-NEXT/baseUtils.cs | 2 +- IOB-WIN-FORM/AdapterForm.cs | 5 ++ IOB-WIN-FORM/IOB-WIN-FORM.csproj | 1 + IOB-WIN-FORM/Iob/Generic.Protected.cs | 12 ++- IOB-WIN-FORM/Iob/Generic.Public.cs | 107 +++++++++++++------------ IOB-WIN-SIEMENS/DATA/CONF/MAIN.ini | 3 +- IOB-WIN-SIEMENS/IOB-WIN-SIEMENS.csproj | 12 ++- 9 files changed, 163 insertions(+), 68 deletions(-) diff --git a/IOB-UT-NEXT/CallMetricsCollector.cs b/IOB-UT-NEXT/CallMetricsCollector.cs index fb8e2296..7fd078df 100644 --- a/IOB-UT-NEXT/CallMetricsCollector.cs +++ b/IOB-UT-NEXT/CallMetricsCollector.cs @@ -1,7 +1,11 @@ -using System; +using Newtonsoft.Json; +using StackExchange.Redis; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; namespace IOB_UT_NEXT { @@ -19,11 +23,46 @@ namespace IOB_UT_NEXT (_, existing) => new CallStats(existing.Count + 1, existing.TotalElapsed.Add(elapsed))); } + /// + /// Carica lo stato persistente da Redis. Thread-safe. + /// + public static async Task LoadFromRedisAsync(IDatabase redisDb, string redisKey = "app:callmetrics") + { + if (redisDb == null) throw new ArgumentNullException(nameof(redisDb)); + ; + var json = await redisDb.StringGetAsync(redisKey); + + if (!json.HasValue) return; + + Dictionary parsed; + try + { + parsed = JsonConvert.DeserializeObject>(json.ToString()); + } + catch (Exception ex) + { + // Log: dati Redis corrotti o schema incompatibile. Si ignora per sicurezza. + return; + } + + if (parsed == null || parsed.Count == 0) return; + + // 5. Applicazione dati sotto lock + lock (_serializationLock) + { + _metrics.Clear(); + foreach (var kvp in parsed) + { + _metrics[kvp.Key] = kvp.Value; + } + } + } + /// /// Metodo per estrarre il report Pareto e reset del contatore. /// /// Se null o vuoto, elabora tutte le chiavi. Altrimenti filtra solo quelle indicate. - public static List LogHourlyPareto(IEnumerable keys = null) + public static List LogPareto(IEnumerable keys = null) { List result; @@ -74,6 +113,43 @@ namespace IOB_UT_NEXT return result; } + /// + /// Salva lo stato corrente in Redis. Thread-safe. + /// + /// Istanza di IDatabase + /// Chiave Redis dove salvare i dati + /// Se true, resetta i contatori dopo il salvataggio + public static async Task SaveToRedisAsync(IDatabase redisDb, string redisKey = "app:callmetrics", bool clearAfterSave = true) + { + if (redisDb == null) throw new ArgumentNullException(nameof(redisDb)); + + // 1. Snapshot atomico sotto lock + Dictionary snapshot; + lock (_serializationLock) + { + snapshot = _metrics.ToDictionary( + kvp => kvp.Key, + kvp => new CallStats(kvp.Value.Count, kvp.Value.TotalElapsed)); + } + + if (snapshot.Count == 0) return; + + // 2. Serializzazione JSON (fuori dal lock) + var json = JsonConvert.SerializeObject(snapshot); + + // 3. Scrittura su Redis (fuori dal lock) + await redisDb.StringSetAsync(redisKey, json); + + // 4. Reset opzionale (sotto lock per sicurezza) + if (clearAfterSave) + { + lock (_serializationLock) + { + _metrics.Clear(); + } + } + } + #endregion Public Methods #region Public Classes @@ -117,6 +193,9 @@ namespace IOB_UT_NEXT // Accumulatore thread-safe private static readonly ConcurrentDictionary _metrics = new ConcurrentDictionary(); + // lock x serializzazione su redisDb + private static readonly object _serializationLock = new object(); + #endregion Private Fields #region Private Classes diff --git a/IOB-UT-NEXT/RedisMan.cs b/IOB-UT-NEXT/RedisMan.cs index e923b3ab..714100a4 100644 --- a/IOB-UT-NEXT/RedisMan.cs +++ b/IOB-UT-NEXT/RedisMan.cs @@ -878,7 +878,7 @@ namespace IOB_UT_NEXT #if false long nCount = currDb.ListLength(queueName); List listData = currDb.ListRange(queueName, 0, nCount).ToList(); - return listData; + return listData; #endif // lettura + reset in blocco var listData = currDb.ListRange(queueName, 0, -1).ToList(); @@ -912,7 +912,7 @@ namespace IOB_UT_NEXT long nCount = currDb.ListLength(queueName); nCount = nCount < maxElem ? nCount : maxElem; List listData = currDb.ListRange(queueName, 0, nCount).ToList(); - return listData; + return listData; #endif // nuovo metodo con rimozione @@ -1451,7 +1451,7 @@ namespace IOB_UT_NEXT return eventId; } -#endregion Public Methods + #endregion Public Methods #region Protected Fields diff --git a/IOB-UT-NEXT/baseUtils.cs b/IOB-UT-NEXT/baseUtils.cs index 3921412d..8f563534 100644 --- a/IOB-UT-NEXT/baseUtils.cs +++ b/IOB-UT-NEXT/baseUtils.cs @@ -1113,7 +1113,7 @@ namespace IOB_UT_NEXT if (!LastHourCurr.Equals(HourCurr)) { // recupero pareto chiamate... - var callList = CallMetricsCollector.LogHourlyPareto(); + var callList = CallMetricsCollector.LogPareto(); if (callList != null) { StringBuilder sb = new StringBuilder(); diff --git a/IOB-WIN-FORM/AdapterForm.cs b/IOB-WIN-FORM/AdapterForm.cs index f395b7f7..ab36e8f9 100644 --- a/IOB-WIN-FORM/AdapterForm.cs +++ b/IOB-WIN-FORM/AdapterForm.cs @@ -1434,6 +1434,11 @@ namespace IOB_WIN_FORM displayTaskAndLog("Waiting for config file selection"); } + // ricarica stats da redis + string callKey = iobObj.redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:CallStats"); + await CallMetricsCollector.LoadFromRedisAsync(iobObj.redisMan.currDb, callKey); + + // Start timer periodico comunicazione gather.Interval = IOBConfFull.General.Timers.MsUI; gather.Enabled = true; diff --git a/IOB-WIN-FORM/IOB-WIN-FORM.csproj b/IOB-WIN-FORM/IOB-WIN-FORM.csproj index c91b39e2..f066e5f4 100644 --- a/IOB-WIN-FORM/IOB-WIN-FORM.csproj +++ b/IOB-WIN-FORM/IOB-WIN-FORM.csproj @@ -61,6 +61,7 @@ ..\packages\NLog.5.3.4\lib\net46\NLog.dll + ..\packages\System.Buffers.4.6.1\lib\net462\System.Buffers.dll diff --git a/IOB-WIN-FORM/Iob/Generic.Protected.cs b/IOB-WIN-FORM/Iob/Generic.Protected.cs index 1e743b05..ce6c221a 100644 --- a/IOB-WIN-FORM/Iob/Generic.Protected.cs +++ b/IOB-WIN-FORM/Iob/Generic.Protected.cs @@ -201,11 +201,6 @@ namespace IOB_WIN_FORM.Iob /// protected bool hasRecipe = false; - /// - /// Elenco delle eventuali condizioni di veto conditions attive - /// - protected List ListVetoCond = new List(); - /// /// Array dei contatori x segnali blinking /// @@ -266,6 +261,11 @@ namespace IOB_WIN_FORM.Iob /// protected List list2Write = new List(); + /// + /// Elenco delle eventuali condizioni di veto conditions attive + /// + protected List ListVetoCond = new List(); + /// /// Num massimo di errori in funzionalità check alive /// @@ -3434,7 +3434,6 @@ namespace IOB_WIN_FORM.Iob } } - /// /// Effettua invio al server IO dei dati di IOB + macchina: /// - dati dell'IOB (IdxMacchina, IobName, IobIp, Tipo, counter...) "classici" @@ -4708,7 +4707,6 @@ namespace IOB_WIN_FORM.Iob /// private string LastDayCurr = ""; - /// /// Ultimo invio valore a server /// diff --git a/IOB-WIN-FORM/Iob/Generic.Public.cs b/IOB-WIN-FORM/Iob/Generic.Public.cs index 49dea9cf..d89c0425 100644 --- a/IOB-WIN-FORM/Iob/Generic.Public.cs +++ b/IOB-WIN-FORM/Iob/Generic.Public.cs @@ -100,57 +100,6 @@ namespace IOB_WIN_FORM.Iob lgError("Error: IobCOnf is null!"); } } - /// - /// Verifica veto invio per una chiave specifica in Redis - /// - /// - /// - public bool CheckSendVeto(string keyReq, TimeSpan vetoReq) - { - DateTime adesso = DateTime.Now; - DateTime lastSend = LastSendGet(keyReq); - bool sendEnab = lastSend.Add(vetoReq) <= adesso; - return sendEnab; - } - - /// - /// Imposta valore datetime x ultimo invio di una chiamata in REDIS HashSet - /// - /// - /// - public bool LastSendSet(string keyReq, DateTime dtRif) - { - string lastSendKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:LastSend"); - string rawVal = JsonConvert.SerializeObject(dtRif); - KeyValuePair[] hashFields = new KeyValuePair[1]; - hashFields[0] = new KeyValuePair(keyReq, rawVal); - bool fatto = redisMan.redSaveHash(lastSendKey, hashFields); - return fatto; - } - - /// - /// Restitusice valore ultimo invio di una chiave, se non esistesse fornisce valore di 1 gg prima e lo salva... - /// - /// - /// - public DateTime LastSendGet(string keyReq) - { - DateTime lastSend = DateTime.Now.AddDays(-1); - string lastSendKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:LastSend"); - string rawVal = redisMan.redGetHashField(lastSendKey, keyReq); - if (!string.IsNullOrEmpty(rawVal)) - { - lastSend = JsonConvert.DeserializeObject(rawVal); - } - else - { - rawVal = JsonConvert.SerializeObject(lastSend); - KeyValuePair[] hashFields = new KeyValuePair[1]; - hashFields[0] = new KeyValuePair(keyReq, rawVal); - redisMan.redSaveHash(lastSendKey, hashFields); - } - return lastSend; - } #endregion Public Constructors @@ -708,6 +657,19 @@ namespace IOB_WIN_FORM.Iob return currentAnsw; } + /// + /// Verifica veto invio per una chiave specifica in Redis + /// + /// + /// + public bool CheckSendVeto(string keyReq, TimeSpan vetoReq) + { + DateTime adesso = DateTime.Now; + DateTime lastSend = LastSendGet(keyReq); + bool sendEnab = lastSend.Add(vetoReq) <= adesso; + return sendEnab; + } + /// /// Verifica se il server sia ALIVE (tramite PING) /// @@ -2088,6 +2050,45 @@ namespace IOB_WIN_FORM.Iob return answ; } + /// + /// Restitusice valore ultimo invio di una chiave, se non esistesse fornisce valore di 1 gg prima e lo salva... + /// + /// + /// + public DateTime LastSendGet(string keyReq) + { + DateTime lastSend = DateTime.Now.AddDays(-1); + string lastSendKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:LastSend"); + string rawVal = redisMan.redGetHashField(lastSendKey, keyReq); + if (!string.IsNullOrEmpty(rawVal)) + { + lastSend = JsonConvert.DeserializeObject(rawVal); + } + else + { + rawVal = JsonConvert.SerializeObject(lastSend); + KeyValuePair[] hashFields = new KeyValuePair[1]; + hashFields[0] = new KeyValuePair(keyReq, rawVal); + redisMan.redSaveHash(lastSendKey, hashFields); + } + return lastSend; + } + + /// + /// Imposta valore datetime x ultimo invio di una chiamata in REDIS HashSet + /// + /// + /// + public bool LastSendSet(string keyReq, DateTime dtRif) + { + string lastSendKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:LastSend"); + string rawVal = JsonConvert.SerializeObject(dtRif); + KeyValuePair[] hashFields = new KeyValuePair[1]; + hashFields[0] = new KeyValuePair(keyReq, rawVal); + bool fatto = redisMan.redSaveHash(lastSendKey, hashFields); + return fatto; + } + /// /// Effettua un trim della stringa al numero max di linee da mostrare a video /// @@ -3770,6 +3771,10 @@ namespace IOB_WIN_FORM.Iob parentForm.displayTaskAndLog("[STOP] Stopping adapter - last periodic data read...", true); + // salvo statistiche + string callKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:CallStats"); + await CallMetricsCollector.SaveToRedisAsync(redisMan.currDb, callKey, false); + // chiudo la connessione all'adapter... tryDisconnect(); dtStopAdp = DateTime.Now; diff --git a/IOB-WIN-SIEMENS/DATA/CONF/MAIN.ini b/IOB-WIN-SIEMENS/DATA/CONF/MAIN.ini index 1a8ea97c..085e681a 100644 --- a/IOB-WIN-SIEMENS/DATA/CONF/MAIN.ini +++ b/IOB-WIN-SIEMENS/DATA/CONF/MAIN.ini @@ -36,8 +36,9 @@ CLI_INST=SteamWareSim ;STARTLIST=PIZ08 ;STARTLIST=iBTB1P ;STARTLIST=3010 -STARTLIST=3020 ;STARTLIST=PIZ03 +;STARTLIST=3020 +STARTLIST=SIMUL_01 MAXCNC=10 diff --git a/IOB-WIN-SIEMENS/IOB-WIN-SIEMENS.csproj b/IOB-WIN-SIEMENS/IOB-WIN-SIEMENS.csproj index f37f8d20..c0f3f370 100644 --- a/IOB-WIN-SIEMENS/IOB-WIN-SIEMENS.csproj +++ b/IOB-WIN-SIEMENS/IOB-WIN-SIEMENS.csproj @@ -242,9 +242,15 @@ Always - - - + + Always + + + Always + + + Always +