S1005403_RisCC/doc/latency_measurement.md

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:

  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:

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

  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

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