8.7 KiB
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:
- Compensazione predittiva: permettere al simulatore di anticipare la posizione futura dei target
- Analisi delle prestazioni: correlare variazioni di latenza con errori di tracking
- Diagnostica: identificare problemi di rete o sovraccarico del sistema
Architettura del Sistema
Componenti Principali
-
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
-
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
-
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:
# 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
# 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:
# 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:
# 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:
# 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:
{
"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:
- Grafico superiore: Errori di tracking (X, Y, Z) nel tempo
- 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
- Latenza asimmetrica: il sistema misura solo la latenza server→client
- Clock resolution: limitata dalla risoluzione di
time.monotonic()(~1ns su Linux, ~15.6ms su Windows vecchi) - Packet reordering: non gestito esplicitamente (assume arrivo in ordine)
Best Practices per Sviluppatori
Aggiungere Nuovi Campioni
# Nel router o comunicatore
clock_sync.add_sample(
raw_server_timetag=packet.timetag,
client_reception_time=time.monotonic()
)
Accedere alle Latenze
# 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
# 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 sincronizzatoretarget_simulator/gui/payload_router.py: Linee 245-260, calcolo latenzatarget_simulator/simulation/simulation_controller.py: Linee 241-247, salvataggio archiviotarget_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