# Misurazione della Latenza Questo documento descrive come il sistema misura la latenza di comunicazione tra il simulatore (client) e il radar (server), e come questi dati vengono utilizzati per l'analisi delle prestazioni. ## Panoramica La latenza rappresenta il ritardo tra il momento in cui il server genera un pacchetto di dati e il momento in cui il client lo riceve. Una misurazione accurata della latenza è fondamentale per: 1. **Compensazione predittiva**: permettere al simulatore di anticipare la posizione futura dei target 2. **Analisi delle prestazioni**: correlare variazioni di latenza con errori di tracking 3. **Diagnostica**: identificare problemi di rete o sovraccarico del sistema ## Architettura del Sistema ### Componenti Principali 1. **ClockSynchronizer** (`target_simulator/utils/clock_synchronizer.py`) - Sincronizza l'orologio del server con quello del client - Gestisce wrap-around del contatore a 32-bit del server - Calcola la latenza media 2. **DebugPayloadRouter** (`target_simulator/gui/payload_router.py`) - Riceve i pacchetti dal server - Alimenta il ClockSynchronizer con campioni di timing - Calcola e memorizza le latenze individuali 3. **SimulationArchive** (`target_simulator/analysis/simulation_archive.py`) - Salva i campioni di latenza con timestamp per analisi post-simulazione ## Come Funziona la Misurazione ### 1. Sincronizzazione degli Orologi Il sistema deve correlare due orologi diversi: - **Server clock**: un contatore a 32-bit che incrementa ad ogni aggiornamento del radar - **Client clock**: `time.monotonic()` del sistema operativo locale #### Problema del Wrap-Around Il contatore del server è a 32-bit (max valore: 4,294,967,295). Quando raggiunge il massimo, "riparte da zero": ``` Server timetag: ... 4,294,967,293 → 4,294,967,294 → 4,294,967,295 → 0 → 1 → 2 ... ``` Il `ClockSynchronizer` rileva questi wrap e "srotola" il contatore: ```python # Rileva wrap se il nuovo valore è molto più piccolo del precedente diff = last_raw_timetag - raw_server_timetag if diff > 2^31: # Metà del range massimo wrap_count += 1 # Valore srotolato continuo unwrapped_timetag = raw_server_timetag + wrap_count * 2^32 ``` ### 2. Modello di Regressione Lineare Il sistema mantiene uno storico delle ultime 100 coppie di valori: ``` (server_timetag_srotolato, client_reception_time) ``` Usa `numpy.polyfit()` per trovare la relazione lineare ottimale: ``` client_time = m * server_ticks + b ``` Dove: - **m** (slope): secondi del client per ogni tick del server (compensazione drift) - **b** (intercept): tempo client quando il server era a zero (offset iniziale) #### Esempio di Fit ```python # Campioni storici (semplificati) server_ticks = [1000000, 1001000, 1002000, 1003000] client_times = [3255.000, 3256.000, 3257.000, 3258.000] # Regressione lineare m, b = np.polyfit(server_ticks, client_times, 1) # Risultato: m ≈ 0.001, b ≈ 2255.0 # Interpretazione: ogni tick del server ≈ 1ms del client ``` ### 3. Calcolo della Latenza per Ogni Pacchetto Quando arriva un nuovo pacchetto: ```python # 1. Nota il tempo di ricezione reception_timestamp = time.monotonic() # es: 3255.123 secondi # 2. Leggi il timetag dal pacchetto del server server_timetag = payload.scenario.timetag # es: 1000121 # 3. Stima quando il pacchetto è stato GENERATO sul server # (convertito in tempo client usando il modello) est_generation_time = clock_sync.to_client_time(server_timetag) # est_generation_time ≈ 3255.121 secondi # 4. Calcola la latenza latency = reception_timestamp - est_generation_time # latency = 3255.123 - 3255.121 = 0.002 secondi = 2 ms ``` ### 4. Latenza Media (per compensazione predittiva) Il `ClockSynchronizer` calcola anche una **latenza media** su tutti i campioni storici: ```python # Per ogni campione nello storico for (server_tick, client_time) in history: # Stima quando il pacchetto doveva essere generato (secondo il modello) estimated_gen = m * server_tick + b # La latenza di quel campione latency_sample = client_time - estimated_gen # Media delle latenze positive (filtra artifact) average_latency = mean(latency_samples[latency_samples >= 0]) ``` Questa latenza media viene: - Visualizzata nell'interfaccia utente come "Avg. Latency" - Salvata nei metadata dell'archivio come `estimated_latency_ms` - Usata per calibrare il `prediction_offset_ms` ## Memorizzazione dei Dati ### Durante la Simulazione Il `DebugPayloadRouter` mantiene un buffer circolare di campioni di latenza: ```python # Buffer con capacità massima di 10000 campioni _latency_samples = collections.deque(maxlen=10000) # Ogni campione è una tupla (timestamp, latency_s) _latency_samples.append((reception_timestamp, latency)) ``` Capacità: 10000 campioni = sufficiente per ~2.7 ore a 1Hz. ### Nell'Archivio Al termine della simulazione, tutti i campioni disponibili vengono salvati nel file JSON: ```json { "metadata": { "estimated_latency_ms": 1.5, "latency_summary": { "mean_ms": 1.5, "std_ms": 0.3, "min_ms": 1.0, "max_ms": 5.0, "count": 1234 }, "latency_samples": [ [3255.123, 1.2], [3255.456, 1.5], [3255.789, 1.3], ... ] } } ``` Formato: `[timestamp_monotonic, latency_ms]` ## Visualizzazione e Analisi ### Finestra di Analisi La finestra di analisi (`AnalysisWindow`) mostra due grafici allineati temporalmente: 1. **Grafico superiore**: Errori di tracking (X, Y, Z) nel tempo 2. **Grafico inferiore**: Evoluzione della latenza nel tempo Entrambi condividono la stessa scala temporale (timestamp monotonic), permettendo di: - Identificare correlazioni tra picchi di latenza e aumenti di errore - Verificare l'efficacia della compensazione predittiva - Diagnosticare problemi di rete o sovraccarico ### Esempio di Analisi ``` Tempo (s) | Latenza (ms) | Errore X (ft) -----------|--------------|--------------- 3255.0 | 1.2 | 5.0 3255.5 | 1.3 | 5.5 3256.0 | 4.5 | 15.0 ← Picco latenza coincide con picco errore 3256.5 | 1.4 | 6.0 3257.0 | 1.2 | 5.2 ``` ## Considerazioni Tecniche ### Precisione della Misurazione - **Risoluzione temporale**: dipende dalla frequenza di aggiornamento del radar - **Accuratezza**: limitata dalla stabilità degli orologi e dal rumore di rete - **Drift compensation**: il modello lineare compensa drift fino a ~100 ppm ### Filtri e Validazione - **Latenze negative**: vengono scartate (artifact del modello durante transitori) - **Outlier**: latenze estreme potrebbero indicare packet loss o riordino - **Sample size**: servono almeno 10 campioni per un fit affidabile ### Limitazioni 1. **Latenza asimmetrica**: il sistema misura solo la latenza server→client 2. **Clock resolution**: limitata dalla risoluzione di `time.monotonic()` (~1ns su Linux, ~15.6ms su Windows vecchi) 3. **Packet reordering**: non gestito esplicitamente (assume arrivo in ordine) ## Best Practices per Sviluppatori ### Aggiungere Nuovi Campioni ```python # Nel router o comunicatore clock_sync.add_sample( raw_server_timetag=packet.timetag, client_reception_time=time.monotonic() ) ``` ### Accedere alle Latenze ```python # Latenza media avg_latency_s = clock_sync.get_average_latency_s() # Campioni recenti samples = router.get_latency_samples(limit=100) for timestamp, latency_s in samples: print(f"t={timestamp:.3f}, latency={latency_s*1000:.1f}ms") ``` ### Salvare nell'Archivio ```python # Automatico in SimulationController._stop_or_finish_simulation() samples = router.get_latency_samples(limit=None) # Tutti i campioni samples_with_time = [ [round(ts, 3), round(lat * 1000.0, 3)] for ts, lat in samples ] extra_metadata["latency_samples"] = samples_with_time ``` ## Riferimenti nel Codice - `target_simulator/utils/clock_synchronizer.py`: Implementazione del sincronizzatore - `target_simulator/gui/payload_router.py`: Linee 245-260, calcolo latenza - `target_simulator/simulation/simulation_controller.py`: Linee 241-247, salvataggio archivio - `target_simulator/gui/analysis_window.py`: Visualizzazione grafico latenze ## FAQ **Q: Perché usiamo un modello lineare invece di una semplice differenza?** A: Gli orologi del server e client hanno drift diversi. Il modello lineare compensa questa deriva nel tempo. **Q: Cosa succede se la rete perde pacchetti?** A: Il modello è robusto grazie ai 100 campioni storici. Pacchetti persi creano gap ma non invalidano il modello. **Q: La latenza misurata include elaborazione lato client?** A: Sì, include tempo di trasmissione + deserializzazione + accodamento. È la latenza "end-to-end" vista dall'applicazione. **Q: Come posso aumentare la capacità del buffer?** A: Modifica `maxlen` in `DebugPayloadRouter.__init__()`. Valore attuale: 10000 campioni. --- *Documento creato: 2025-11-13* *Ultima modifica: 2025-11-13*