feat(ui): Build Diagnostics Panel with real-time plots
This commit is contained in:
69
gui.py
69
gui.py
@@ -460,6 +460,7 @@ class App:
|
||||
"Theme": "win_theme",
|
||||
"Last Script Output": "win_script_output",
|
||||
"Text Viewer": "win_text_viewer",
|
||||
"Diagnostics": "win_diagnostics",
|
||||
}
|
||||
|
||||
|
||||
@@ -496,6 +497,12 @@ class App:
|
||||
self._script_blink_start_time = 0.0
|
||||
|
||||
self.perf_monitor = PerformanceMonitor()
|
||||
self.perf_history = {
|
||||
"frame_time": [0.0] * 100,
|
||||
"fps": [0.0] * 100,
|
||||
"cpu": [0.0] * 100,
|
||||
"input_lag": [0.0] * 100
|
||||
}
|
||||
|
||||
session_logger.open_session()
|
||||
ai_client.set_provider(self.current_provider, self.current_model)
|
||||
@@ -815,6 +822,32 @@ class App:
|
||||
else:
|
||||
dpg.configure_item("gemini_cache_label", show=False)
|
||||
|
||||
# Update Diagnostics panel
|
||||
if dpg.is_item_shown("win_diagnostics"):
|
||||
metrics = self.perf_monitor.get_metrics()
|
||||
|
||||
# Update history
|
||||
self.perf_history["frame_time"].append(metrics['last_frame_time_ms'])
|
||||
self.perf_history["fps"].append(metrics['fps'])
|
||||
self.perf_history["cpu"].append(metrics['cpu_percent'])
|
||||
self.perf_history["input_lag"].append(metrics['input_lag_ms'])
|
||||
|
||||
for k in self.perf_history:
|
||||
if len(self.perf_history[k]) > 100:
|
||||
self.perf_history[k].pop(0)
|
||||
|
||||
# Update labels
|
||||
dpg.set_value("perf_fps_text", f"{metrics['fps']:.1f}")
|
||||
dpg.set_value("perf_frame_text", f"{metrics['last_frame_time_ms']:.1f}ms")
|
||||
dpg.set_value("perf_cpu_text", f"{metrics['cpu_percent']:.1f}%")
|
||||
dpg.set_value("perf_lag_text", f"{metrics['input_lag_ms']:.1f}ms")
|
||||
|
||||
# Update plots
|
||||
if dpg.does_item_exist("perf_frame_plot"):
|
||||
dpg.set_value("perf_frame_plot", [list(range(100)), self.perf_history["frame_time"]])
|
||||
if dpg.does_item_exist("perf_cpu_plot"):
|
||||
dpg.set_value("perf_cpu_plot", [list(range(100)), self.perf_history["cpu"]])
|
||||
|
||||
def _append_comms_entry(self, entry: dict, idx: int):
|
||||
if not dpg.does_item_exist("comms_scroll"):
|
||||
return
|
||||
@@ -2110,6 +2143,42 @@ class App:
|
||||
with dpg.child_window(tag="text_viewer_wrap_container", width=-1, height=-1, border=False, show=False):
|
||||
dpg.add_text("", tag="text_viewer_wrap", wrap=0)
|
||||
|
||||
# ---- Diagnostics panel ----
|
||||
with dpg.window(
|
||||
label="Diagnostics",
|
||||
tag="win_diagnostics",
|
||||
pos=(8, 804),
|
||||
width=400,
|
||||
height=380,
|
||||
no_close=False,
|
||||
):
|
||||
dpg.add_text("Performance Telemetry")
|
||||
with dpg.group(horizontal=True):
|
||||
dpg.add_text("FPS:")
|
||||
dpg.add_text("0.0", tag="perf_fps_text", color=(180, 255, 180))
|
||||
dpg.add_spacer(width=20)
|
||||
dpg.add_text("Frame:")
|
||||
dpg.add_text("0.0ms", tag="perf_frame_text", color=(100, 200, 255))
|
||||
|
||||
with dpg.add_plot(label="Frame Time (ms)", height=100, width=-1, no_mouse_pos=True):
|
||||
dpg.add_plot_axis(dpg.mvXAxis, label="samples", no_tick_labels=True)
|
||||
with dpg.plot_axis(dpg.mvYAxis, label="ms"):
|
||||
dpg.add_line_series(list(range(100)), self.perf_history["frame_time"], label="frame time", tag="perf_frame_plot")
|
||||
dpg.set_axis_limits(dpg.last_item(), 0, 50)
|
||||
|
||||
with dpg.group(horizontal=True):
|
||||
dpg.add_text("CPU:")
|
||||
dpg.add_text("0.0%", tag="perf_cpu_text", color=(255, 220, 100))
|
||||
dpg.add_spacer(width=20)
|
||||
dpg.add_text("Input Lag:")
|
||||
dpg.add_text("0.0ms", tag="perf_lag_text", color=(255, 180, 80))
|
||||
|
||||
with dpg.add_plot(label="CPU Usage (%)", height=100, width=-1, no_mouse_pos=True):
|
||||
dpg.add_plot_axis(dpg.mvXAxis, label="samples", no_tick_labels=True)
|
||||
with dpg.plot_axis(dpg.mvYAxis, label="%"):
|
||||
dpg.add_line_series(list(range(100)), self.perf_history["cpu"], label="cpu usage", tag="perf_cpu_plot")
|
||||
dpg.set_axis_limits(dpg.last_item(), 0, 100)
|
||||
|
||||
def run(self):
|
||||
dpg.create_context()
|
||||
dpg.configure_app(docking=True, docking_space=True, init_file="dpg_layout.ini")
|
||||
|
||||
65
tests/test_gui_diagnostics.py
Normal file
65
tests/test_gui_diagnostics.py
Normal file
@@ -0,0 +1,65 @@
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
import importlib.util
|
||||
import sys
|
||||
import dearpygui.dearpygui as dpg
|
||||
|
||||
# Load gui.py as a module for testing
|
||||
spec = importlib.util.spec_from_file_location("gui", "gui.py")
|
||||
gui = importlib.util.module_from_spec(spec)
|
||||
sys.modules["gui"] = gui
|
||||
spec.loader.exec_module(gui)
|
||||
from gui import App
|
||||
|
||||
@pytest.fixture
|
||||
def app_instance():
|
||||
dpg.create_context()
|
||||
with patch('dearpygui.dearpygui.create_viewport'), \
|
||||
patch('dearpygui.dearpygui.setup_dearpygui'), \
|
||||
patch('dearpygui.dearpygui.show_viewport'), \
|
||||
patch('dearpygui.dearpygui.start_dearpygui'), \
|
||||
patch('gui.load_config', return_value={}), \
|
||||
patch.object(App, '_rebuild_files_list'), \
|
||||
patch.object(App, '_rebuild_shots_list'), \
|
||||
patch.object(App, '_rebuild_disc_list'), \
|
||||
patch.object(App, '_rebuild_disc_roles_list'), \
|
||||
patch.object(App, '_rebuild_discussion_selector'), \
|
||||
patch.object(App, '_refresh_project_widgets'):
|
||||
|
||||
app = App()
|
||||
yield app
|
||||
dpg.destroy_context()
|
||||
|
||||
def test_diagnostics_panel_initialization(app_instance):
|
||||
assert "Diagnostics" in app_instance.window_info
|
||||
assert app_instance.window_info["Diagnostics"] == "win_diagnostics"
|
||||
assert "frame_time" in app_instance.perf_history
|
||||
assert len(app_instance.perf_history["frame_time"]) == 100
|
||||
|
||||
def test_diagnostics_panel_updates(app_instance):
|
||||
# Mock dependencies
|
||||
mock_metrics = {
|
||||
'last_frame_time_ms': 10.0,
|
||||
'fps': 100.0,
|
||||
'cpu_percent': 50.0,
|
||||
'input_lag_ms': 5.0
|
||||
}
|
||||
app_instance.perf_monitor.get_metrics = MagicMock(return_value=mock_metrics)
|
||||
|
||||
with patch('dearpygui.dearpygui.is_item_shown', return_value=True), \
|
||||
patch('dearpygui.dearpygui.set_value') as mock_set_value, \
|
||||
patch('dearpygui.dearpygui.configure_item') as mock_configure_item, \
|
||||
patch('dearpygui.dearpygui.does_item_exist', return_value=True):
|
||||
|
||||
# We also need to mock ai_client stats
|
||||
with patch('ai_client.get_history_bleed_stats', return_value={}):
|
||||
app_instance._update_telemetry_panel()
|
||||
|
||||
# Verify UI updates
|
||||
mock_set_value.assert_any_call("perf_fps_text", "100.0")
|
||||
mock_set_value.assert_any_call("perf_frame_text", "10.0ms")
|
||||
mock_set_value.assert_any_call("perf_cpu_text", "50.0%")
|
||||
mock_set_value.assert_any_call("perf_lag_text", "5.0ms")
|
||||
|
||||
# Verify history update
|
||||
assert app_instance.perf_history["frame_time"][-1] == 10.0
|
||||
Reference in New Issue
Block a user