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