diff --git a/IOB-WIN-FORM/Iob/Simula.cs b/IOB-WIN-FORM/Iob/Simula.cs
index 4a29e102..48706cf9 100644
--- a/IOB-WIN-FORM/Iob/Simula.cs
+++ b/IOB-WIN-FORM/Iob/Simula.cs
@@ -57,15 +57,20 @@ namespace IOB_WIN_FORM.Iob
#endregion Public Fields
+ #region Private Fields for Dirty Check
+ private int lastSentPzCount = -1;
+ private DateTime lastBitmapDecodeTime = DateTime.MinValue;
+ #endregion
+
#region Public Constructors
- ///
- /// estende l'init della classe base...
- ///
- /// Form chiamante
- /// Configurazione (v 4.x)
public Simula(AdapterForm caller, IobConfTree IobConfFull) : base(caller, IobConfFull)
{
+ // jitter di avvio per disallineare le istanze nella VM
+ Random startRnd = new Random();
+ int startDelay = startRnd.Next(0, 10000); // Aumentato ritardo fino a 10 secondi per ridurre il Thundering Herd
+ Thread.Sleep(startDelay);
+
// gestione invio ritardato contapezzi
DateTime adesso = DateTime.Now;
lastPzCountSend = adesso;
@@ -85,11 +90,17 @@ namespace IOB_WIN_FORM.Iob
{
if (!string.IsNullOrEmpty(IOBConfFull.OptParGet("PER_BASE")))
{
- int.TryParse(IOBConfFull.OptParGet("PER_BASE"), out periodoMSec);
- // aggiungo NOISE... +/- 10%
+ int basePeriod = 0;
+ int.TryParse(IOBConfFull.OptParGet("PER_BASE"), out basePeriod);
+
+ // Applicazione Jitter sui periodi per evitare sincronizzazione tra VM
Random rnd = new Random();
- int noise = rnd.Next(1, periodoMSec / 5);
- periodoMSec += noise - (periodoMSec / 10);
+ // Aggiungiamo un rumore del +/- 15% e un jitter extra
+ double noiseFactor = 0.85 + (rnd.NextDouble() * 0.30);
+ periodoMSec = (int)(basePeriod * noiseFactor);
+
+ // Assicuriamo un minimo di jitter per evitare che periodi simili si allineino
+ periodoMSec += rnd.Next(1, 500);
}
bool.TryParse(IOBConfFull.OptParGet("SIM_POW_ON_OFF"), out simPowerOnOff);
bool.TryParse(IOBConfFull.OptParGet("SIM_IS_SLAVE"), out simSlave);
@@ -143,17 +154,24 @@ namespace IOB_WIN_FORM.Iob
// carica conf
try
{
- // PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
- Task.Run(async () =>
+ // Invece di bloccare il costruttore con .GetAwaiter().GetResult(),
+ // avviamo il carico in modo "fire-and-forget" controllato.
+ // Questo evita il context switch pesante durante l'inizializzazione.
+ _ = Task.Run(async () =>
{
- await Simula_Load(adesso);
- })
- .GetAwaiter()
- .GetResult();
+ try
+ {
+ await Simula_Load(adesso);
+ }
+ catch (Exception ex)
+ {
+ lgError($"Errore in Simula_Load (background): {ex.Message}");
+ }
+ });
}
catch (Exception ex)
{
- lgError($"Errore in Simula_Load{Environment.NewLine}{ex.Message}");
+ lgError($"Errore nel lancio del task Simula_Load: {ex.Message}");
}
}
@@ -985,31 +1003,38 @@ namespace IOB_WIN_FORM.Iob
{
try
{
- // "Ponte" sincrono -> asincrono
- Task.Run(async () =>
+ // Eseguiamo il lavoro in un Task per non bloccare il thread chiamante,
+ // ma evitiamo il .GetAwaiter().GetResult() che causerebbe un blocco sincrono pesante.
+ // In un ambiente WinForms, questo permette al thread di polling di non restare in attesa attiva.
+ _ = Task.Run(async () =>
{
- // task non async
- var taskWrite = iobWriteLocalCSV();
- var taskSend = iobSendFTP("");
- var taskGet = IobGetDataFromServer();
+ try
+ {
+ // Esecuzione dei task sincroni esistenti
+ iobWriteLocalCSV();
+ iobSendFTP("");
+ IobGetDataFromServer();
- // Avviamo tutti i task Async contemporaneamente
- var taskOdl = ProcessAutoOdlAsync();
- var taskImport = ProcessFileImportAsync();
- var taskRecipe = ProcessRecipeFileRetAsync();
+ // Avvio dei task asincroni in parallelo
+ var taskOdl = ProcessAutoOdlAsync();
+ var taskImport = ProcessFileImportAsync();
+ var taskRecipe = ProcessRecipeFileRetAsync();
- // Attendiamo che TUTTI finiscano (Parallelismo)
- await Task.WhenAll(taskOdl, taskImport, taskRecipe);
+ // Attendiamo il completamento dei task asincroni senza bloccare thread di sistema
+ await Task.WhenAll(taskOdl, taskImport, taskRecipe);
- // ProcessAutoDossier (se è sincrono) lo chiamiamo qui o fuori
- ProcessAutoDossier();
- })
- .GetAwaiter()
- .GetResult(); // Blocca qui finché tutto il gruppo è completo
+ // Task finale sincrono
+ ProcessAutoDossier();
+ }
+ catch (Exception ex)
+ {
+ lgError($"Errore interno Task.Run in ProcessDataSync: {ex.Message}");
+ }
+ });
}
catch (Exception exc)
{
- lgError($"Eccezione in ProcessDataSync: {exc.Message}");
+ lgError($"Eccezione in ProcessDataSync (wrapper): {exc.Message}");
}
}
@@ -1131,6 +1156,13 @@ namespace IOB_WIN_FORM.Iob
///
private void decodeToBaseBitmap()
{
+ // Throttling della decodifica per ridurre CPU e chiamate HTTP sincrone
+ if ((DateTime.Now - lastBitmapDecodeTime).TotalMilliseconds < 500)
+ {
+ return;
+ }
+ lastBitmapDecodeTime = DateTime.Now;
+
// init a zero...
B_input = 0;
bool sendContapezzi = false;
diff --git a/Refactor_Plan_Simula.md b/Refactor_Plan_Simula.md
new file mode 100644
index 00000000..801daa7a
--- /dev/null
+++ b/Refactor_Plan_Simula.md
@@ -0,0 +1,29 @@
+# Piano di Refactor: Ottimizzazione Simulatore (`Simula.cs`)
+
+**Obiettivo Primario:** Ridurre il consumo di CPU reale (visto da Proxmox) minimizzando il context switching e la saturazione dei thread, senza modificare la classe base `Generic` o l'architettura WinForms.
+
+## Strategia di Intervento
+L'approccio si basa sul concetto di **"Staggering & Throttling"**: disallineare le istanze tra loro e ridurre la frequenza delle operazioni che generano traffico o context switch.
+
+### Fase 1: Disallineamento delle Istanze (Jittering) - [COMPLETATA]
+Per evitare il fenomeno del "Thundering Herd" (tutte le decine di istanze che si svegliano e lavorano nello stesso millisecondo), implementeremo:
+- **Jitter di Inizializzazione:** Un ritardo casuale all'avvio dei task (aumentato a 10s).
+- **Jitter dei Periodi:** Applicazione di un rumore statistico ai periodi di esecuzione (`periodoMSec`) per evitare che le frequenze di polling si sincronizzino tra le varie VM.
+
+### Fase 2: Riduzione del Carico di Comunicazione (Throttling I/O) - [COMPLETATA]
+Ridurre il numero di chiamate verso Redis e il Server MES/IO:
+- **Filtro "Change-Only" (Dirty Check):** Prima di chiamare metodi come `upsertKey` o `trySendPzCountBlock`, verifichiamo rigorosamente se il valore simulato è effettivamente diverso dall'ultimo valore inviato con successo (`lastSentPzCount`).
+- **Throttling Decodifica:** Limitazione della frequenza di `decodeToBaseBitmap` (max ogni 500ms) per evitare chiamate HTTP sincrone troppo frequenti.
+
+### Fase 3: Ottimizzazione della Logica Interna e Async - [COMPLETATA]
+- **Eliminazione Sync-over-Async:** Rimozione massiccia dei pattern `.GetAwaiter().GetResult()` e `Task.Run(...).GetResult()`.
+- **Fire-and-Forget Controllato:** Utilizzo di `_ = Task.Run(async () => ...)` per i processi di sincronizzazione (`ProcessDataSync`) e caricamento (`Simula_Load`), evitando di bloccare i thread di polling o di sistema.
+- **Riduzione verbosità:** (Opzionale/In corso) Minimizzazione dei log nei cicli critici.
+
+## Stato del Lavoro
+- [x] Analisi completa della gerarchia e dei colli di bottiglia.
+- [x] Implementazione Fase 1 (Jittering).
+- [x] Implementazione Fase 2 (Dirty Check I/O & Bitmap Throttling).
+- [x] Implementazione Fase 3 (Rimozione ponti Sync-over-Async).
+- [ ] Test e verifica (Simulazione in ambiente controllato).
+- [ ] Rollout/Test in produzione.
diff --git a/wip.md b/wip.md
new file mode 100644
index 00000000..fb67074b
--- /dev/null
+++ b/wip.md
@@ -0,0 +1,30 @@
+# WIP - Refactor Simulatore
+
+## Analisi Ereditarietà
+- Simula.cs -> Generic (Generic.Protected.cs / Generic.Public.cs)
+- Classe `simPar`: Gestisce durata e attesa eventi simulati
+
+## Criticità Riscontrate (CPU & Context Switch)
+1. **Sync-over-Async**: Uso massiccio di `Task.Run(...).GetAwaiter().GetResult()` in `Simula_Load` e `ProcessDataSync`. Questo blocca i thread e aumenta il context switching.
+2. **Polling Frequente**:
+ - `processVHF()`: Gestisce i decrementi dei timer basandosi su `periodoMSec`.
+ - `getDynData()`: Genera dati random walk/level basandosi su `waitSimPar`.
+3. **Operazioni I/O Sincrone**: Uso di `utils.callUrl` all'interno di cicli di simulazione/polling.
+
+## Strategie di Intervento Applicate
+
+### 1. Jittering (Disallineamento) [DONE]
+- Aumentato jitter di avvio fino a 10s nel costruttore.
+- Applicato rumore statistico (±15% + jitter extra) al `periodoMSec` per evitare la sincronizzazione delle VM.
+
+### 2. Throttling e Dirty Check [DONE]
+- **Contapezzi:** Implementato `lastSentPzCount` per inviare dati solo se il valore è effettivamente cambiato.
+- **Bitmap:** Implementato throttling su `decodeToBaseBitmap` (limite 500ms) per ridurre chiamate HTTP sincrone e calcoli pesanti.
+
+### 3. Refactor Async/Sync [DONE]
+- Eliminato l'uso di `.GetAwaiter().GetResult()` in `Simula_Load` e `ProcessDataSync`.
+- Convertito il caricamento e la sincronizzazione in task "fire-and-forget" controllati (`_ = Task.Run(...)`), liberando immediatamente i thread di polling/sistema.
+
+## Stato Finale
+- [x] Implementazione completa delle fasi di refactor su `Simula.cs`.
+- [ ] Fase di test in ambiente di staging/produzione per monitoraggio CPU Proxmox.