diff --git a/settings.json b/settings.json index 185f923..f29ff89 100644 --- a/settings.json +++ b/settings.json @@ -2,7 +2,7 @@ "general": { "scan_limit": 60, "max_range": 100, - "geometry": "1492x992+230+258", + "geometry": "1492x992+459+100", "last_selected_scenario": "scenario_dritto", "connection": { "target": { diff --git a/target_simulator/gui/main_view.py b/target_simulator/gui/main_view.py index 71e620f..32f8828 100644 --- a/target_simulator/gui/main_view.py +++ b/target_simulator/gui/main_view.py @@ -194,7 +194,15 @@ class MainView(tk.Tk): self.protocol("WM_DELETE_WINDOW", self._on_closing) self.logger.info("MainView initialized successfully.") - self.after(GUI_REFRESH_RATE_MS, self._gui_refresh_loop) + # Allow overriding the GUI refresh interval from settings.json. + # Support both `GUI_REFRESH_RATE_MS` and `gui_refresh_rate_ms` keys + try: + configured = settings.get("GUI_REFRESH_RATE_MS", settings.get("gui_refresh_rate_ms", None)) + self.gui_refresh_rate_ms = int(configured) if configured is not None else GUI_REFRESH_RATE_MS + except Exception: + self.gui_refresh_rate_ms = GUI_REFRESH_RATE_MS + + self.after(self.gui_refresh_rate_ms, self._gui_refresh_loop) self.after(1000, self._update_rate_status) self.after(1000, self._update_latency_status) @@ -846,7 +854,12 @@ class MainView(tk.Tk): if hasattr(self, "ppi_widget") and self.ppi_widget.canvas: self.ppi_widget.canvas.draw_idle() - self.after(GUI_REFRESH_RATE_MS, self._gui_refresh_loop) + # Schedule next GUI refresh using configured interval + try: + self.after(self.gui_refresh_rate_ms, self._gui_refresh_loop) + except Exception: + # Fallback to module-level default if instance attr missing + self.after(GUI_REFRESH_RATE_MS, self._gui_refresh_loop) def _refresh_analysis_list(self): """Refreshes the list of archived simulation runs in the analysis tab.""" diff --git a/target_simulator/gui/sfp_debug_window.py b/target_simulator/gui/sfp_debug_window.py index 5a765cc..b69599f 100644 --- a/target_simulator/gui/sfp_debug_window.py +++ b/target_simulator/gui/sfp_debug_window.py @@ -160,8 +160,22 @@ class SfpDebugWindow(tk.Toplevel): if self.shared_communicator: self._update_toggle_state(self.shared_communicator.is_open) + + # Allow overriding the debug window poll interval from the main settings.json. + # Support both `GUI_POLL_INTERVAL_MS` and `gui_poll_interval_ms` keys. + try: + general = None + if hasattr(self.master, "config_manager") and self.master.config_manager: + general = self.master.config_manager.get_general_settings() or {} + configured = None + if general is not None: + configured = general.get("GUI_POLL_INTERVAL_MS", general.get("gui_poll_interval_ms", None)) + self.gui_poll_interval_ms = int(configured) if configured is not None else self.GUI_POLL_INTERVAL_MS + except Exception: + self.gui_poll_interval_ms = self.GUI_POLL_INTERVAL_MS + # Schedule the legacy polling loop used by tests and older UI code - self.after(self.GUI_POLL_INTERVAL_MS, self._process_latest_payloads) + self.after(self.gui_poll_interval_ms, self._process_latest_payloads) def _queue_ris_target_update(self, targets: List[Target]): """Enqueue a list of RIS targets for later processing on the GUI thread. @@ -355,7 +369,11 @@ class SfpDebugWindow(tk.Toplevel): except Exception: self.logger.exception("Error while fetching raw packet from router") - self.after(self.GUI_POLL_INTERVAL_MS, self._process_gui_updates) + # use configured poll interval if available + try: + self.after(self.gui_poll_interval_ms, self._process_gui_updates) + except Exception: + self.after(self.GUI_POLL_INTERVAL_MS, self._process_gui_updates) def _create_widgets(self): """Create and lay out all child widgets for the debug window. diff --git a/target_simulator/gui/status_bar.py b/target_simulator/gui/status_bar.py index 0863adb..b79b3db 100644 --- a/target_simulator/gui/status_bar.py +++ b/target_simulator/gui/status_bar.py @@ -202,12 +202,41 @@ class StatusBar(ttk.Frame): while not self._res_stop_event.wait(self._resource_poll_s): try: - cpu = psutil.cpu_percent(None) - rss = proc.memory_info().rss - rss_mb = rss / (1024.0 * 1024.0) + # Measure CPU usage for the current process (proc.cpu_percent) + # psutil.Process.cpu_percent may return values >100 on + # multi-core systems because it reports percentage of CPU + # time across all cores. To present a value comparable to + # Task Manager's per-process percentage (0-100 scale), + # normalize by the number of logical CPUs. + cpu_proc = proc.cpu_percent(None) + ncpu = psutil.cpu_count(logical=True) or 1 + cpu = cpu_proc / ncpu + + # Prefer USS (unique set size) when available since it + # represents memory unique to the process (closer to + # what Task Manager reports as private working set). + try: + mem_full = proc.memory_full_info() + uss = getattr(mem_full, "uss", None) + except Exception: + uss = None + + if uss is not None and uss > 0: + mem_bytes = uss + mem_tag = "USS" + else: + # Fallback to RSS (working set) if USS unavailable + mem_bytes = proc.memory_info().rss + mem_tag = "RSS" + + # Convert to MiB for display (1024^2) — clear label + rss_mb = mem_bytes / (1024.0 * 1024.0) mem_pct = proc.memory_percent() nthreads = proc.num_threads() - s = f"CPU {cpu:.0f}% · MEM {rss_mb:.0f}MB ({mem_pct:.1f}%) · Thr {nthreads}" + s = ( + f"CPU {cpu:.0f}% · MEM {rss_mb:.0f}MB ({mem_pct:.1f}%)" + f" [{mem_tag}] · Thr {nthreads}" + ) except Exception: s = ""