Update appunti + modifiche simula da testare

This commit is contained in:
Samuele E. Locatelli (W11-AI)
2026-05-19 17:05:15 +02:00
parent e3689f5ec7
commit c5116fa529
3 changed files with 125 additions and 34 deletions
+66 -34
View File
@@ -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
/// <summary>
/// estende l'init della classe base...
/// </summary>
/// <param name="caller">Form chiamante</param>
/// <param name="IobConfFull">Configurazione (v 4.x)</param>
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
/// </summary>
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;
+29
View File
@@ -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.
+30
View File
@@ -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.