From 5908e72ae3da9451d2963f2e24d16d12d47655ea Mon Sep 17 00:00:00 2001 From: VALLONGOL Date: Mon, 17 Nov 2025 16:01:11 +0100 Subject: [PATCH] aggiunto il salvataggio dei dati del debug delle latenze nei messaggi di sync in csv --- logger_prefs.json | 6 + settings.json | 2 +- target_simulator/gui/ppi_adapter.py | 4 - target_simulator/gui/sync_tool_window.py | 187 +++++++++++++++++++++++ 4 files changed, 194 insertions(+), 5 deletions(-) create mode 100644 logger_prefs.json diff --git a/logger_prefs.json b/logger_prefs.json new file mode 100644 index 0000000..64a2a15 --- /dev/null +++ b/logger_prefs.json @@ -0,0 +1,6 @@ +{ + "saved_levels": { + "target_simulator.core.sfp_transport": "INFO" + }, + "last_selected": "target_simulator.core.sfp_transport" +} \ No newline at end of file diff --git a/settings.json b/settings.json index 05d2391..185f923 100644 --- a/settings.json +++ b/settings.json @@ -3,7 +3,7 @@ "scan_limit": 60, "max_range": 100, "geometry": "1492x992+230+258", - "last_selected_scenario": "scenario2", + "last_selected_scenario": "scenario_dritto", "connection": { "target": { "type": "sfp", diff --git a/target_simulator/gui/ppi_adapter.py b/target_simulator/gui/ppi_adapter.py index a7c8eae..120086d 100644 --- a/target_simulator/gui/ppi_adapter.py +++ b/target_simulator/gui/ppi_adapter.py @@ -127,10 +127,6 @@ def build_display_data( t_scenario = scenario.get_target(tid) if t_scenario is not None: 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) diff --git a/target_simulator/gui/sync_tool_window.py b/target_simulator/gui/sync_tool_window.py index 1c0d62d..2fbd72b 100644 --- a/target_simulator/gui/sync_tool_window.py +++ b/target_simulator/gui/sync_tool_window.py @@ -3,8 +3,12 @@ import tkinter as tk from tkinter import ttk, messagebox import time import random +import os +import csv +import threading from typing import Dict, Optional, List from collections import deque +from datetime import datetime from target_simulator.core.sfp_communicator import SFPCommunicator from target_simulator.gui.payload_router import DebugPayloadRouter @@ -51,6 +55,15 @@ class SyncToolWindow(tk.Toplevel): # Statistiche 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.protocol("WM_DELETE_WINDOW", self._on_close) @@ -96,6 +109,25 @@ class SyncToolWindow(tk.Toplevel): 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 === stats_frame = ttk.LabelFrame(main_frame, text="Statistics", padding=5) stats_frame.pack(fill=tk.X, pady=(0, 10)) @@ -320,6 +352,14 @@ class SyncToolWindow(tk.Toplevel): if len(self.latency_values) > 200: 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 timestamp_str = time.strftime("%H:%M:%S") @@ -343,10 +383,157 @@ class SyncToolWindow(tk.Toplevel): # Continua il polling 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): """Gestisce la chiusura della finestra.""" if self.periodic_active: self.periodic_active = False if 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()