diff --git a/conductor/tracks/ui_performance_20260223/plan.md b/conductor/tracks/ui_performance_20260223/plan.md index bdb7d9c..5de9ffa 100644 --- a/conductor/tracks/ui_performance_20260223/plan.md +++ b/conductor/tracks/ui_performance_20260223/plan.md @@ -25,7 +25,7 @@ - [x] Task: Build the Diagnostics Panel in Dear PyGui 30d838c - [x] Sub-task: Write Tests (verify panel components render) - [x] Sub-task: Implement Feature (plots, stat readouts in `gui.py`) -- [ ] Task: Identify and fix main thread performance bottlenecks +- [~] Task: Identify and fix main thread performance bottlenecks - [ ] Sub-task: Write Tests (reproducible "heavy" load test) - [ ] Sub-task: Implement Feature (refactor heavy logic to workers) - [ ] Task: Conductor - User Manual Verification 'Phase 3: Diagnostics UI and Optimization' (Protocol in workflow.md) \ No newline at end of file diff --git a/gui.py b/gui.py index 4e0abc1..539027e 100644 --- a/gui.py +++ b/gui.py @@ -504,6 +504,13 @@ class App: "input_lag": [0.0] * 100 } + self.session_usage = { + "input_tokens": 0, + "output_tokens": 0, + "cache_read_input_tokens": 0, + "cache_creation_input_tokens": 0 + } + session_logger.open_session() ai_client.set_provider(self.current_provider, self.current_model) ai_client.confirm_and_run_callback = self._confirm_and_run @@ -511,6 +518,8 @@ class App: ai_client.tool_log_callback = self._on_tool_log mcp_client.perf_monitor_callback = self.perf_monitor.get_metrics self.perf_monitor.alert_callback = self._on_performance_alert + self._last_bleed_update_time = 0 + self._recalculate_session_usage() # ---------------------------------------------------------------- project loading @@ -775,6 +784,21 @@ class App: "ts": project_manager.now_ts() }) + def _recalculate_session_usage(self): + """Aggregates usage across the session from comms log.""" + usage = { + "input_tokens": 0, + "output_tokens": 0, + "cache_read_input_tokens": 0, + "cache_creation_input_tokens": 0 + } + for entry in ai_client.get_comms_log(): + if entry.get("kind") == "response" and "usage" in entry.get("payload", {}): + u = entry["payload"]["usage"] + for k in usage.keys(): + usage[k] += u.get(k, 0) or 0 + self.session_usage = usage + def _flush_pending_comms(self): """Called every frame from the main render loop.""" with self._pending_comms_lock: @@ -784,26 +808,30 @@ class App: self._comms_entry_count += 1 self._append_comms_entry(entry, self._comms_entry_count) if entries: + self._recalculate_session_usage() self._update_token_usage() def _update_token_usage(self): if not dpg.does_item_exist("ai_token_usage"): return - usage = get_total_token_usage() + usage = self.session_usage total = usage["input_tokens"] + usage["output_tokens"] dpg.set_value("ai_token_usage", f"Tokens: {total} (In: {usage['input_tokens']} Out: {usage['output_tokens']})") def _update_telemetry_panel(self): """Updates the token budget visualizer in the Provider panel.""" - # Update history bleed stats for all providers - stats = ai_client.get_history_bleed_stats() - if dpg.does_item_exist("token_budget_bar"): - percentage = stats.get("percentage", 0.0) - dpg.set_value("token_budget_bar", percentage / 100.0 if percentage else 0.0) - if dpg.does_item_exist("token_budget_label"): - current = stats.get("current", 0) - limit = stats.get("limit", 0) - dpg.set_value("token_budget_label", f"{current:,} / {limit:,}") + # Update history bleed stats for all providers (throttled) + now = time.time() + if now - self._last_bleed_update_time > 2.0: + self._last_bleed_update_time = now + stats = ai_client.get_history_bleed_stats() + if dpg.does_item_exist("token_budget_bar"): + percentage = stats.get("percentage", 0.0) + dpg.set_value("token_budget_bar", percentage / 100.0 if percentage else 0.0) + if dpg.does_item_exist("token_budget_label"): + current = stats.get("current", 0) + limit = stats.get("limit", 0) + dpg.set_value("token_budget_label", f"{current:,} / {limit:,}") # Update Gemini-specific cache stats if dpg.does_item_exist("gemini_cache_label"): diff --git a/project.toml b/project.toml index c8a99bb..1095f9f 100644 --- a/project.toml +++ b/project.toml @@ -35,5 +35,5 @@ active = "main" [discussion.discussions.main] git_commit = "" -last_updated = "2026-02-23T14:48:16" +last_updated = "2026-02-23T14:52:20" history = [] diff --git a/tests/test_gui_updates.py b/tests/test_gui_updates.py index 0083767..ba349d0 100644 --- a/tests/test_gui_updates.py +++ b/tests/test_gui_updates.py @@ -56,9 +56,11 @@ def test_telemetry_panel_updates_correctly(app_instance): } # 3. Patch the dependencies + app_instance._last_bleed_update_time = 0 # Force update with patch('ai_client.get_history_bleed_stats', return_value=mock_stats) as mock_get_stats, \ patch('dearpygui.dearpygui.set_value') as mock_set_value, \ patch('dearpygui.dearpygui.configure_item') as mock_configure_item, \ + patch('dearpygui.dearpygui.is_item_shown', return_value=False), \ patch('dearpygui.dearpygui.does_item_exist', return_value=True) as mock_does_item_exist: # 4. Call the method under test @@ -91,9 +93,11 @@ def test_cache_data_display_updates_correctly(app_instance): expected_text = "Gemini Caches: 5 (12.1 KB)" # 3. Patch dependencies + app_instance._last_bleed_update_time = 0 # Force update with patch('ai_client.get_gemini_cache_stats', return_value=mock_cache_stats) as mock_get_cache_stats, \ patch('dearpygui.dearpygui.set_value') as mock_set_value, \ patch('dearpygui.dearpygui.configure_item') as mock_configure_item, \ + patch('dearpygui.dearpygui.is_item_shown', return_value=False), \ patch('dearpygui.dearpygui.does_item_exist', return_value=True) as mock_does_item_exist: # We also need to mock get_history_bleed_stats as it's called in the same function