S1005403_RisCC/doc/latency_measurement.md

282 lines
8.7 KiB
Markdown

# 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*