Compare commits

..

2 Commits

4 changed files with 131 additions and 11 deletions

View File

@@ -84,9 +84,14 @@ This file tracks all major tracks for the project. Each track has its own detail
19. [ ] **Track: Manual UX Validation & Review**
*Link: [./tracks/manual_ux_validation_20260302/](./tracks/manual_ux_validation_20260302/)*
### Misc Side-tracks
20. [x] **Track: Enhanced Context Control & Cache Awareness**
*Link: [./tracks/enhanced_context_control_20260307/](./tracks/enhanced_context_control_20260307/)*
22. [~] **Track: GUI Performance Profiling & Optimization**
*Link: [./tracks/gui_performance_profiling_20260307/](./tracks/gui_performance_profiling_20260307/)*
---
## Completed / Archived

View File

@@ -0,0 +1,23 @@
# Implementation Plan: GUI Performance Profiling & Optimization (gui_performance_profiling_20260307)
> **Reference:** [Spec](./spec.md) | [Architecture Guide](../../../docs/guide_architecture.md)
## Phase 1: Instrumentation
Focus: Add profiling hooks to gui_2.py
- [x] Task 1.1: Wrap `_render_log_management` with profiling calls. (f27b971)
- [x] Task 1.2: Wrap `_render_discussion_panel` with profiling calls. (f27b971)
- [x] Task 1.3: Wrap `_render_mma_dashboard` with profiling calls. (f27b971)
- [x] Task 1.4: Wrap core `_gui_func` logic with profiling calls. (f27b971)
## Phase 2: Diagnostics UI
Focus: Display timings in the GUI
- [x] Task 2.1: Add "Detailed Component Timings" table to Diagnostics panel in `src/gui_2.py`. (f27b971)
- [x] Task 2.2: Implement 10ms threshold highlighting in the table. (f27b971)
## Phase 3: Verification & Optimization
Focus: Analyze results and fix bottlenecks
- [ ] Task 3.1: Verify timings are accurate via manual walkthrough.
- [ ] Task 3.2: Identify components consistently > 10ms and propose optimizations.

View File

@@ -0,0 +1,21 @@
# Track Specification: GUI Performance Profiling & Optimization (gui_performance_profiling_20260307)
## Overview
Implement fine-grained performance profiling within the main ImGui rendering loop (`gui_2.py`) to ensure adherence to data-oriented and immediate mode heuristics. This track will provide visual diagnostics for high-overhead UI components, allowing developers to monitor and optimize render frame times.
## Core Requirements
1. **Instrumentation:** Inject `start_component()` and `end_component()` calls from the `PerformanceMonitor` API (`src/performance_monitor.py`) around identified high-overhead methods in `src/gui_2.py`.
2. **Diagnostics UI:** Expand the Diagnostics panel in `gui_2.py` to include a new table titled "Detailed Component Timings".
3. **Threshold Alerting:** Add visual threshold alerts (e.g., color highlighting) in the new Diagnostics table for any individual component whose execution time exceeds 10ms.
4. **Target Methods:**
- `_render_log_management`
- `_render_discussion_panel`
- `_render_mma_dashboard`
- `_gui_func` (as a global wrapper)
## Acceptance Criteria
- [ ] Profiling calls correctly wrap target methods.
- [ ] "Detailed Component Timings" table displays in Diagnostics panel.
- [ ] Timings update in real-time (every 0.5s or similar).
- [ ] Components exceeding 10ms are highlighted (e.g., Red).
- [ ] 1-space indentation maintained.

View File

@@ -114,6 +114,8 @@ class App:
self._tool_log_dirty: bool = True
self._last_ui_focus_agent: Optional[str] = None
self._log_registry: Optional[log_registry.LogRegistry] = None
self.perf_profiling_enabled = False
self.perf_profiling_enabled = False
def _handle_approve_tool(self, user_data=None) -> None:
"""UI-level wrapper for approving a pending tool execution ask."""
@@ -241,6 +243,7 @@ class App:
imgui.end_menu()
def _gui_func(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_gui_func")
try:
self.perf_monitor.start_frame()
self._autofocus_response_tab = self.controller._autofocus_response_tab
@@ -342,7 +345,9 @@ class App:
exp, opened = imgui.begin("MMA Dashboard", self.show_windows["MMA Dashboard"])
self.show_windows["MMA Dashboard"] = bool(opened)
if exp:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_mma_dashboard")
self._render_mma_dashboard()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_mma_dashboard")
imgui.end()
if self.show_windows.get("Tier 1: Strategy", False):
exp, opened = imgui.begin("Tier 1: Strategy", self.show_windows["Tier 1: Strategy"])
@@ -376,7 +381,9 @@ class App:
if exp:
# Top part for the history
imgui.begin_child("HistoryChild", size=(0, -200))
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_discussion_panel")
self._render_discussion_panel()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_discussion_panel")
imgui.end_child()
# Bottom part with tabs for message and response
# Detach controls
@@ -472,7 +479,9 @@ class App:
imgui.end()
if self.show_windows.get("Log Management", False):
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_log_management")
self._render_log_management()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_log_management")
if self.show_windows["Diagnostics"]:
exp, opened = imgui.begin("Diagnostics", self.show_windows["Diagnostics"])
self.show_windows["Diagnostics"] = bool(opened)
@@ -491,6 +500,8 @@ class App:
self.perf_history["input_lag"].append(metrics.get("input_lag_ms", 0.0))
metrics = self.perf_monitor.get_metrics()
imgui.text("Performance Telemetry")
imgui.same_line()
_, self.perf_profiling_enabled = imgui.checkbox("Enable Profiling", self.perf_profiling_enabled)
imgui.separator()
if imgui.begin_table("perf_table", 2, imgui.TableFlags_.borders_inner_h):
imgui.table_setup_column("Metric")
@@ -517,6 +528,66 @@ class App:
imgui.table_next_column()
imgui.text(f"{metrics.get('input_lag_ms', 0.0):.1f}")
imgui.end_table()
if self.perf_profiling_enabled:
imgui.separator()
imgui.text("Detailed Component Timings")
if imgui.begin_table("comp_timings", 2, imgui.TableFlags_.borders):
imgui.table_setup_column("Component")
imgui.table_setup_column("Time (ms)")
imgui.table_headers_row()
for key, val in metrics.items():
if key.startswith("time_") and key.endswith("_ms"):
comp_name = key[5:-3]
imgui.table_next_row()
imgui.table_next_column()
imgui.text(comp_name)
imgui.table_next_column()
if val > 10.0:
imgui.text_colored(imgui.ImVec4(1.0, 0.2, 0.2, 1.0), f"{val:.2f}")
else:
imgui.text(f"{val:.2f}")
imgui.end_table()
if self.perf_profiling_enabled:
imgui.separator()
imgui.text("Detailed Component Timings")
if imgui.begin_table("comp_timings", 2, imgui.TableFlags_.borders):
imgui.table_setup_column("Component")
imgui.table_setup_column("Time (ms)")
imgui.table_headers_row()
for key, val in metrics.items():
if key.startswith("time_") and key.endswith("_ms"):
comp_name = key[5:-3]
imgui.table_next_row()
imgui.table_next_column()
imgui.text(comp_name)
imgui.table_next_column()
if val > 10.0:
imgui.text_colored(imgui.ImVec4(1.0, 0.2, 0.2, 1.0), f"{val:.2f}")
else:
imgui.text(f"{val:.2f}")
imgui.end_table()
if self.perf_profiling_enabled:
imgui.separator()
imgui.text("Detailed Component Timings")
if imgui.begin_table("comp_timings", 2, imgui.TableFlags_.borders):
imgui.table_setup_column("Component")
imgui.table_setup_column("Time (ms)")
imgui.table_headers_row()
for key, val in metrics.items():
if key.startswith("time_") and key.endswith("_ms"):
comp_name = key[5:-3]
imgui.table_next_row()
imgui.table_next_column()
imgui.text(comp_name)
imgui.table_next_column()
if val > 10.0:
imgui.text_colored(imgui.ImVec4(1.0, 0.2, 0.2, 1.0), f"{val:.2f}")
else:
imgui.text(f"{val:.2f}")
imgui.end_table()
imgui.separator()
imgui.text("Frame Time (ms)")
imgui.plot_lines("##ft_plot", np.array(self.perf_history["frame_time"], dtype=np.float32), overlay_text="frame_time", graph_size=imgui.ImVec2(-1, 60))
@@ -1012,8 +1083,6 @@ class App:
if imgui.button("Refresh Registry"):
self._log_registry = log_registry.LogRegistry(str(paths.get_logs_dir() / "log_registry.toml"))
imgui.same_line()
if imgui.button("Force Prune Logs"):
self.controller.event_queue.put("gui_task", {"action": "click", "item": "btn_prune_logs"})
registry = self._log_registry
sessions = registry.data
@@ -1068,6 +1137,8 @@ class App:
)
imgui.end_table()
if imgui.button("Force Prune Logs"):
self.controller.event_queue.put("gui_task", {"action": "click", "item": "btn_prune_logs"})
imgui.end()
def _render_files_panel(self) -> None:
@@ -1084,11 +1155,11 @@ class App:
r.destroy()
if d: self.ui_files_base_dir = d
imgui.separator()
imgui.begin_child("f_paths", imgui.ImVec2(0, 200), True)
if imgui.begin_table("files_table", 4, imgui.TableFlags_.resizable | imgui.TableFlags_.borders | imgui.TableFlags_.scroll_x):
imgui.begin_child("f_paths", imgui.ImVec2(0, -40), True)
if imgui.begin_table("files_table", 4, imgui.TableFlags_.resizable | imgui.TableFlags_.borders):
imgui.table_setup_column("Actions", imgui.TableColumnFlags_.width_fixed, 40)
imgui.table_setup_column("File Path", imgui.TableColumnFlags_.width_stretch)
imgui.table_setup_column("Flags", imgui.TableColumnFlags_.width_fixed, 110)
imgui.table_setup_column("Flags", imgui.TableColumnFlags_.width_fixed, 150)
imgui.table_setup_column("Cache", imgui.TableColumnFlags_.width_fixed, 40)
imgui.table_headers_row()
@@ -1146,7 +1217,7 @@ class App:
r.destroy()
if d: self.ui_shots_base_dir = d
imgui.separator()
imgui.begin_child("s_paths", imgui.ImVec2(0, 200), True)
imgui.begin_child("s_paths", imgui.ImVec2(0, -40), True)
for i, s in enumerate(self.screenshots):
if imgui.button(f"x##s{i}"):
self.screenshots.pop(i)
@@ -1619,21 +1690,21 @@ class App:
with self._send_thread_lock:
if self.send_thread and self.send_thread.is_alive():
send_busy = True
if imgui.button("Inject File"):
self.show_inject_modal = True
imgui.same_line()
if (imgui.button("Gen + Send") or ctrl_enter) and not send_busy:
self._handle_generate_send()
imgui.same_line()
if imgui.button("MD Only"):
self._handle_md_only()
imgui.same_line()
if imgui.button("Inject File"):
self.show_inject_modal = True
if imgui.button("Reset"):
self._handle_reset_session()
imgui.same_line()
if imgui.button("-> History"):
if self.ui_ai_input:
self.disc_entries.append({"role": "User", "content": self.ui_ai_input, "collapsed": False, "ts": project_manager.now_ts()})
imgui.same_line()
if imgui.button("Reset"):
self._handle_reset_session()
def _render_response_panel(self) -> None:
if self._trigger_blink: