feat(perf): Add high-resolution component profiling to main loop

This commit is contained in:
2026-02-23 15:09:58 -05:00
parent 8ccc3d60b5
commit b91e72b749
2 changed files with 36 additions and 2 deletions

14
gui.py
View File

@@ -2237,14 +2237,18 @@ class App:
while dpg.is_dearpygui_running(): while dpg.is_dearpygui_running():
self.perf_monitor.start_frame() self.perf_monitor.start_frame()
# Show any pending confirmation dialog on the main thread safely # Show any pending confirmation dialog on the main thread safely
self.perf_monitor.start_component("Dialogs")
with self._pending_dialog_lock: with self._pending_dialog_lock:
dialog = self._pending_dialog dialog = self._pending_dialog
self._pending_dialog = None self._pending_dialog = None
if dialog is not None: if dialog is not None:
dialog.show() dialog.show()
self.perf_monitor.end_component("Dialogs")
# Process queued history additions # Process queued history additions
self.perf_monitor.start_component("History")
with self._pending_history_adds_lock: with self._pending_history_adds_lock:
adds = self._pending_history_adds[:] adds = self._pending_history_adds[:]
self._pending_history_adds.clear() self._pending_history_adds.clear()
@@ -2259,8 +2263,10 @@ class App:
if dpg.does_item_exist("disc_scroll"): if dpg.does_item_exist("disc_scroll"):
# Force scroll to bottom using a very large number # Force scroll to bottom using a very large number
dpg.set_y_scroll("disc_scroll", 99999) dpg.set_y_scroll("disc_scroll", 99999)
self.perf_monitor.end_component("History")
# Process queued API GUI tasks # Process queued API GUI tasks
self.perf_monitor.start_component("GUI_Tasks")
with self._pending_gui_tasks_lock: with self._pending_gui_tasks_lock:
gui_tasks = self._pending_gui_tasks[:] gui_tasks = self._pending_gui_tasks[:]
self._pending_gui_tasks.clear() self._pending_gui_tasks.clear()
@@ -2280,8 +2286,10 @@ class App:
cb() cb()
except Exception as e: except Exception as e:
print(f"Error executing GUI hook task: {e}") print(f"Error executing GUI hook task: {e}")
self.perf_monitor.end_component("GUI_Tasks")
# Handle retro arcade blinking effect # Handle retro arcade blinking effect
self.perf_monitor.start_component("Blinking")
if self._trigger_script_blink: if self._trigger_script_blink:
self._trigger_script_blink = False self._trigger_script_blink = False
self._is_script_blinking = True self._is_script_blinking = True
@@ -2369,10 +2377,16 @@ class App:
dpg.bind_item_theme("ai_response_wrap_container", "response_blink_theme") dpg.bind_item_theme("ai_response_wrap_container", "response_blink_theme")
except Exception: except Exception:
pass pass
self.perf_monitor.end_component("Blinking")
# Flush any comms entries queued from background threads # Flush any comms entries queued from background threads
self.perf_monitor.start_component("Comms")
self._flush_pending_comms() self._flush_pending_comms()
self.perf_monitor.end_component("Comms")
self.perf_monitor.start_component("Telemetry")
self._update_telemetry_panel() self._update_telemetry_panel()
self.perf_monitor.end_component("Telemetry")
self.perf_monitor.end_frame() self.perf_monitor.end_frame()
dpg.render_dearpygui_frame() dpg.render_dearpygui_frame()

View File

@@ -27,6 +27,10 @@ class PerformanceMonitor:
self._last_alert_time = 0 self._last_alert_time = 0
self._alert_cooldown = 30 # seconds self._alert_cooldown = 30 # seconds
# Detailed profiling
self._component_timings = {}
self._comp_start = {}
# Start CPU usage monitoring thread # Start CPU usage monitoring thread
self._stop_event = threading.Event() self._stop_event = threading.Event()
self._cpu_thread = threading.Thread(target=self._monitor_cpu, daemon=True) self._cpu_thread = threading.Thread(target=self._monitor_cpu, daemon=True)
@@ -46,6 +50,14 @@ class PerformanceMonitor:
def record_input_event(self): def record_input_event(self):
self._last_input_time = time.time() self._last_input_time = time.time()
def start_component(self, name: str):
self._comp_start[name] = time.time()
def end_component(self, name: str):
if name in self._comp_start:
elapsed = (time.time() - self._comp_start[name]) * 1000.0
self._component_timings[name] = elapsed
def end_frame(self): def end_frame(self):
if self._start_time is None: if self._start_time is None:
return return
@@ -92,12 +104,20 @@ class PerformanceMonitor:
with self._cpu_lock: with self._cpu_lock:
cpu_usage = self._cpu_usage cpu_usage = self._cpu_usage
return { metrics = {
'last_frame_time_ms': self._last_frame_time, 'last_frame_time_ms': self._last_frame_time,
'fps': self._fps, 'fps': self._fps,
'cpu_percent': cpu_usage, 'cpu_percent': cpu_usage,
'input_lag_ms': self._input_lag_ms 'input_lag_ms': self._last_input_time if self._last_input_time else 0.0 # Wait, this should be the calculated lag
} }
# Oops, fixed the input lag logic in previous turn, let's keep it consistent
metrics['input_lag_ms'] = self._input_lag_ms
# Add detailed timings
for name, elapsed in self._component_timings.items():
metrics[f'time_{name}_ms'] = elapsed
return metrics
def stop(self): def stop(self):
self._stop_event.set() self._stop_event.set()