Aggiunta gestione dati callStat persistenti su Redis
This commit is contained in:
@@ -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)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Carica lo stato persistente da Redis. Thread-safe.
|
||||
/// </summary>
|
||||
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<string, CallStats> parsed;
|
||||
try
|
||||
{
|
||||
parsed = JsonConvert.DeserializeObject<Dictionary<string, CallStats>>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Metodo per estrarre il report Pareto e reset del contatore.
|
||||
/// </summary>
|
||||
/// <param name="keys">Se null o vuoto, elabora tutte le chiavi. Altrimenti filtra solo quelle indicate.</param>
|
||||
public static List<ParetoEntry> LogHourlyPareto(IEnumerable<string> keys = null)
|
||||
public static List<ParetoEntry> LogPareto(IEnumerable<string> keys = null)
|
||||
{
|
||||
List<ParetoEntry> result;
|
||||
|
||||
@@ -74,6 +113,43 @@ namespace IOB_UT_NEXT
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Salva lo stato corrente in Redis. Thread-safe.
|
||||
/// </summary>
|
||||
/// <param name="redisDb">Istanza di IDatabase</param>
|
||||
/// <param name="redisKey">Chiave Redis dove salvare i dati</param>
|
||||
/// <param name="clearAfterSave">Se true, resetta i contatori dopo il salvataggio</param>
|
||||
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<string, CallStats> 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<string, CallStats> _metrics = new ConcurrentDictionary<string, CallStats>();
|
||||
|
||||
// lock x serializzazione su redisDb
|
||||
private static readonly object _serializationLock = new object();
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Private Classes
|
||||
|
||||
@@ -878,7 +878,7 @@ namespace IOB_UT_NEXT
|
||||
#if false
|
||||
long nCount = currDb.ListLength(queueName);
|
||||
List<RedisValue> 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<RedisValue> 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
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
<HintPath>..\packages\NLog.5.3.4\lib\net46\NLog.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PresentationFramework" />
|
||||
<Reference Include="StackExchange.Redis, Version=2.0.0.0, Culture=neutral, PublicKeyToken=c219ff1ca8c2ce46" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Buffers, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Buffers.4.6.1\lib\net462\System.Buffers.dll</HintPath>
|
||||
|
||||
@@ -201,11 +201,6 @@ namespace IOB_WIN_FORM.Iob
|
||||
/// </summary>
|
||||
protected bool hasRecipe = false;
|
||||
|
||||
/// <summary>
|
||||
/// Elenco delle eventuali condizioni di veto conditions attive
|
||||
/// </summary>
|
||||
protected List<string> ListVetoCond = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Array dei contatori x segnali blinking
|
||||
/// </summary>
|
||||
@@ -266,6 +261,11 @@ namespace IOB_WIN_FORM.Iob
|
||||
/// </summary>
|
||||
protected List<objItem> list2Write = new List<objItem>();
|
||||
|
||||
/// <summary>
|
||||
/// Elenco delle eventuali condizioni di veto conditions attive
|
||||
/// </summary>
|
||||
protected List<string> ListVetoCond = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Num massimo di errori in funzionalità check alive
|
||||
/// </summary>
|
||||
@@ -3434,7 +3434,6 @@ namespace IOB_WIN_FORM.Iob
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
private string LastDayCurr = "";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Ultimo invio valore a server
|
||||
/// </summary>
|
||||
|
||||
@@ -100,57 +100,6 @@ namespace IOB_WIN_FORM.Iob
|
||||
lgError("Error: IobCOnf is null!");
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Verifica veto invio per una chiave specifica in Redis
|
||||
/// </summary>
|
||||
/// <param name="keyReq"></param>
|
||||
/// <returns></returns>
|
||||
public bool CheckSendVeto(string keyReq, TimeSpan vetoReq)
|
||||
{
|
||||
DateTime adesso = DateTime.Now;
|
||||
DateTime lastSend = LastSendGet(keyReq);
|
||||
bool sendEnab = lastSend.Add(vetoReq) <= adesso;
|
||||
return sendEnab;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Imposta valore datetime x ultimo invio di una chiamata in REDIS HashSet
|
||||
/// </summary>
|
||||
/// <param name="keyReq"></param>
|
||||
/// <param name="dtRif"></param>
|
||||
public bool LastSendSet(string keyReq, DateTime dtRif)
|
||||
{
|
||||
string lastSendKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:LastSend");
|
||||
string rawVal = JsonConvert.SerializeObject(dtRif);
|
||||
KeyValuePair<string, string>[] hashFields = new KeyValuePair<string, string>[1];
|
||||
hashFields[0] = new KeyValuePair<string, string>(keyReq, rawVal);
|
||||
bool fatto = redisMan.redSaveHash(lastSendKey, hashFields);
|
||||
return fatto;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restitusice valore ultimo invio di una chiave, se non esistesse fornisce valore di 1 gg prima e lo salva...
|
||||
/// </summary>
|
||||
/// <param name="keyReq"></param>
|
||||
/// <returns></returns>
|
||||
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<DateTime>(rawVal);
|
||||
}
|
||||
else
|
||||
{
|
||||
rawVal = JsonConvert.SerializeObject(lastSend);
|
||||
KeyValuePair<string, string>[] hashFields = new KeyValuePair<string, string>[1];
|
||||
hashFields[0] = new KeyValuePair<string, string>(keyReq, rawVal);
|
||||
redisMan.redSaveHash(lastSendKey, hashFields);
|
||||
}
|
||||
return lastSend;
|
||||
}
|
||||
|
||||
#endregion Public Constructors
|
||||
|
||||
@@ -708,6 +657,19 @@ namespace IOB_WIN_FORM.Iob
|
||||
return currentAnsw;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifica veto invio per una chiave specifica in Redis
|
||||
/// </summary>
|
||||
/// <param name="keyReq"></param>
|
||||
/// <returns></returns>
|
||||
public bool CheckSendVeto(string keyReq, TimeSpan vetoReq)
|
||||
{
|
||||
DateTime adesso = DateTime.Now;
|
||||
DateTime lastSend = LastSendGet(keyReq);
|
||||
bool sendEnab = lastSend.Add(vetoReq) <= adesso;
|
||||
return sendEnab;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifica se il server sia ALIVE (tramite PING)
|
||||
/// </summary>
|
||||
@@ -2088,6 +2050,45 @@ namespace IOB_WIN_FORM.Iob
|
||||
return answ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restitusice valore ultimo invio di una chiave, se non esistesse fornisce valore di 1 gg prima e lo salva...
|
||||
/// </summary>
|
||||
/// <param name="keyReq"></param>
|
||||
/// <returns></returns>
|
||||
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<DateTime>(rawVal);
|
||||
}
|
||||
else
|
||||
{
|
||||
rawVal = JsonConvert.SerializeObject(lastSend);
|
||||
KeyValuePair<string, string>[] hashFields = new KeyValuePair<string, string>[1];
|
||||
hashFields[0] = new KeyValuePair<string, string>(keyReq, rawVal);
|
||||
redisMan.redSaveHash(lastSendKey, hashFields);
|
||||
}
|
||||
return lastSend;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Imposta valore datetime x ultimo invio di una chiamata in REDIS HashSet
|
||||
/// </summary>
|
||||
/// <param name="keyReq"></param>
|
||||
/// <param name="dtRif"></param>
|
||||
public bool LastSendSet(string keyReq, DateTime dtRif)
|
||||
{
|
||||
string lastSendKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:LastSend");
|
||||
string rawVal = JsonConvert.SerializeObject(dtRif);
|
||||
KeyValuePair<string, string>[] hashFields = new KeyValuePair<string, string>[1];
|
||||
hashFields[0] = new KeyValuePair<string, string>(keyReq, rawVal);
|
||||
bool fatto = redisMan.redSaveHash(lastSendKey, hashFields);
|
||||
return fatto;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Effettua un trim della stringa al numero max di linee da mostrare a video
|
||||
/// </summary>
|
||||
@@ -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;
|
||||
|
||||
@@ -36,8 +36,9 @@ CLI_INST=SteamWareSim
|
||||
;STARTLIST=PIZ08
|
||||
;STARTLIST=iBTB1P
|
||||
;STARTLIST=3010
|
||||
STARTLIST=3020
|
||||
;STARTLIST=PIZ03
|
||||
;STARTLIST=3020
|
||||
STARTLIST=SIMUL_01
|
||||
|
||||
MAXCNC=10
|
||||
|
||||
|
||||
@@ -242,9 +242,15 @@
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="DATA\CONF\SIEMENS.ini" />
|
||||
<None Include="DATA\CONF\SIMUL_01.ini" />
|
||||
<None Include="DATA\CONF\SIMUL_01.json" />
|
||||
<None Include="DATA\CONF\SIMUL_01_alarm.json" />
|
||||
<None Include="DATA\CONF\SIMUL_01.ini">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="DATA\CONF\SIMUL_01.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="DATA\CONF\SIMUL_01_alarm.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="DATA\CONF\SIMUL_01_WD.ini" />
|
||||
<None Include="DATA\CONF\VALV_SAET.ini" />
|
||||
<None Include="DATA\CONF\VL20.ini" />
|
||||
|
||||
Reference in New Issue
Block a user