aggiunto il salvataggio dei dati del debug delle latenze nei messaggi di sync in csv

This commit is contained in:
VALLONGOL 2025-11-17 16:01:11 +01:00
parent 3052757f43
commit 5908e72ae3
4 changed files with 194 additions and 5 deletions

6
logger_prefs.json Normal file
View File

@ -0,0 +1,6 @@
{
"saved_levels": {
"target_simulator.core.sfp_transport": "INFO"
},
"last_selected": "target_simulator.core.sfp_transport"
}

View File

@ -3,7 +3,7 @@
"scan_limit": 60, "scan_limit": 60,
"max_range": 100, "max_range": 100,
"geometry": "1492x992+230+258", "geometry": "1492x992+230+258",
"last_selected_scenario": "scenario2", "last_selected_scenario": "scenario_dritto",
"connection": { "connection": {
"target": { "target": {
"type": "sfp", "type": "sfp",

View File

@ -127,10 +127,6 @@ def build_display_data(
t_scenario = scenario.get_target(tid) t_scenario = scenario.get_target(tid)
if t_scenario is not None: if t_scenario is not None:
sim_target.active = bool(getattr(t_scenario, "active", True)) sim_target.active = bool(getattr(t_scenario, "active", True))
if logger and not sim_target.active:
logger.debug(
f"PPI Adapter: Target {tid} is INACTIVE (from scenario fallback)"
)
simulated_targets_for_ppi.append(sim_target) simulated_targets_for_ppi.append(sim_target)

View File

@ -3,8 +3,12 @@ import tkinter as tk
from tkinter import ttk, messagebox from tkinter import ttk, messagebox
import time import time
import random import random
import os
import csv
import threading
from typing import Dict, Optional, List from typing import Dict, Optional, List
from collections import deque from collections import deque
from datetime import datetime
from target_simulator.core.sfp_communicator import SFPCommunicator from target_simulator.core.sfp_communicator import SFPCommunicator
from target_simulator.gui.payload_router import DebugPayloadRouter from target_simulator.gui.payload_router import DebugPayloadRouter
@ -51,6 +55,15 @@ class SyncToolWindow(tk.Toplevel):
# Statistiche # Statistiche
self.latency_values: List[float] = [] self.latency_values: List[float] = []
# === Sistema di salvataggio CSV (asincrono e non invasivo) ===
self.csv_enabled = False
self.csv_filepath: Optional[str] = None
self.csv_buffer: deque = deque(maxlen=10000) # Buffer grande per evitare perdite
self.csv_lock = threading.Lock()
self.csv_writer_thread: Optional[threading.Thread] = None
self.csv_stop_event = threading.Event()
self.csv_session_start: Optional[float] = None
self._create_widgets() self._create_widgets()
self.protocol("WM_DELETE_WINDOW", self._on_close) self.protocol("WM_DELETE_WINDOW", self._on_close)
@ -96,6 +109,25 @@ class SyncToolWindow(tk.Toplevel):
side=tk.LEFT side=tk.LEFT
) )
# Riga 3: Salvataggio CSV
row3 = ttk.Frame(controls_frame)
row3.pack(fill=tk.X, pady=2)
self.csv_check_var = tk.BooleanVar(value=False)
self.csv_checkbox = ttk.Checkbutton(
row3,
text="Save to CSV",
variable=self.csv_check_var,
command=self._toggle_csv_save,
)
self.csv_checkbox.pack(side=tk.LEFT, padx=(0, 10))
self.csv_status_var = tk.StringVar(value="Not saving")
self.csv_status_label = tk.Label(
row3, textvariable=self.csv_status_var, foreground="gray"
)
self.csv_status_label.pack(side=tk.LEFT)
# === STATISTICHE === # === STATISTICHE ===
stats_frame = ttk.LabelFrame(main_frame, text="Statistics", padding=5) stats_frame = ttk.LabelFrame(main_frame, text="Statistics", padding=5)
stats_frame.pack(fill=tk.X, pady=(0, 10)) stats_frame.pack(fill=tk.X, pady=(0, 10))
@ -320,6 +352,14 @@ class SyncToolWindow(tk.Toplevel):
if len(self.latency_values) > 200: if len(self.latency_values) > 200:
self.latency_values.pop(0) self.latency_values.pop(0)
# Salva su CSV se abilitato (operazione velocissima, non blocca)
if self.csv_enabled and self.csv_session_start is not None:
elapsed_s = reception_time - self.csv_session_start
server_timetag = result.get("server_timetag", 0)
self._add_sample_to_csv_buffer(
elapsed_s, cookie, rtt_ms, latency_ms, server_timetag
)
# Aggiorna UI # Aggiorna UI
timestamp_str = time.strftime("%H:%M:%S") timestamp_str = time.strftime("%H:%M:%S")
@ -343,10 +383,157 @@ class SyncToolWindow(tk.Toplevel):
# Continua il polling # Continua il polling
self.after(100, self._process_sync_queue) self.after(100, self._process_sync_queue)
def _toggle_csv_save(self):
"""Attiva/disattiva il salvataggio CSV asincrono."""
if self.csv_check_var.get():
# Avvia salvataggio
self._start_csv_save()
else:
# Ferma salvataggio
self._stop_csv_save()
def _start_csv_save(self):
"""Avvia il salvataggio CSV in background."""
if self.csv_enabled:
return # Già attivo
# Crea nome file con timestamp
project_root = os.path.abspath(
os.path.join(os.path.dirname(__file__), "..", "..")
)
temp_dir = os.path.join(project_root, "Temp")
os.makedirs(temp_dir, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
self.csv_filepath = os.path.join(temp_dir, f"sync_latency_{timestamp}.csv")
self.csv_session_start = time.monotonic()
# Inizializza il file CSV con header
try:
with open(self.csv_filepath, "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(
[
"elapsed_s",
"timestamp",
"cookie",
"rtt_ms",
"latency_ms",
"server_timetag",
]
)
self.csv_enabled = True
self.csv_stop_event.clear()
# Avvia thread di scrittura
self.csv_writer_thread = threading.Thread(
target=self._csv_writer_loop, daemon=True, name="CSVWriterThread"
)
self.csv_writer_thread.start()
filename = os.path.basename(self.csv_filepath)
self.csv_status_var.set(f"Saving to {filename}")
self.csv_status_label.config(foreground="green")
except Exception as e:
messagebox.showerror(
"CSV Error", f"Failed to create CSV file: {e}", parent=self
)
self.csv_check_var.set(False)
self.csv_enabled = False
def _stop_csv_save(self):
"""Ferma il salvataggio CSV."""
if not self.csv_enabled:
return
self.csv_enabled = False
self.csv_stop_event.set()
# Attendi che il thread finisca di scrivere i dati rimanenti
if self.csv_writer_thread and self.csv_writer_thread.is_alive():
self.csv_writer_thread.join(timeout=2.0)
self.csv_status_var.set("Not saving")
self.csv_status_label.config(foreground="gray")
if self.csv_filepath:
filename = os.path.basename(self.csv_filepath)
messagebox.showinfo(
"CSV Saved",
f"Latency data saved to:\n{filename}",
parent=self,
)
def _csv_writer_loop(self):
"""Thread worker che scrive i dati bufferizzati su disco periodicamente."""
while not self.csv_stop_event.is_set():
try:
# Attendi un po' prima di scrivere (batch writing)
time.sleep(1.0)
# Estrai tutti i dati dal buffer
rows_to_write = []
with self.csv_lock:
while len(self.csv_buffer) > 0:
rows_to_write.append(self.csv_buffer.popleft())
# Scrivi su file (fuori dal lock per non bloccare)
if rows_to_write and self.csv_filepath:
try:
with open(self.csv_filepath, "a", newline="") as f:
writer = csv.writer(f)
writer.writerows(rows_to_write)
except Exception:
pass # Ignora errori di scrittura per non bloccare
except Exception:
pass
# Flush finale dei dati rimanenti
rows_to_write = []
with self.csv_lock:
while len(self.csv_buffer) > 0:
rows_to_write.append(self.csv_buffer.popleft())
if rows_to_write and self.csv_filepath:
try:
with open(self.csv_filepath, "a", newline="") as f:
writer = csv.writer(f)
writer.writerows(rows_to_write)
except Exception:
pass
def _add_sample_to_csv_buffer(
self, elapsed_s: float, cookie: int, rtt_ms: float, latency_ms: float, server_timetag: int
):
"""Aggiunge un campione al buffer CSV (operazione velocissima, non blocca)."""
if not self.csv_enabled:
return
timestamp_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
row = [
f"{elapsed_s:.3f}",
timestamp_str,
cookie,
f"{rtt_ms:.3f}",
f"{latency_ms:.3f}",
server_timetag,
]
with self.csv_lock:
self.csv_buffer.append(row)
def _on_close(self): def _on_close(self):
"""Gestisce la chiusura della finestra.""" """Gestisce la chiusura della finestra."""
if self.periodic_active: if self.periodic_active:
self.periodic_active = False self.periodic_active = False
if self.periodic_after_id: if self.periodic_after_id:
self.after_cancel(self.periodic_after_id) self.after_cancel(self.periodic_after_id)
# Ferma il salvataggio CSV se attivo
if self.csv_enabled:
self.csv_check_var.set(False)
self._stop_csv_save()
self.destroy() self.destroy()