feat(perf): Implement performance threshold alert system
This commit is contained in:
12
gui.py
12
gui.py
@@ -502,6 +502,7 @@ class App:
|
|||||||
ai_client.comms_log_callback = self._on_comms_entry
|
ai_client.comms_log_callback = self._on_comms_entry
|
||||||
ai_client.tool_log_callback = self._on_tool_log
|
ai_client.tool_log_callback = self._on_tool_log
|
||||||
mcp_client.perf_monitor_callback = self.perf_monitor.get_metrics
|
mcp_client.perf_monitor_callback = self.perf_monitor.get_metrics
|
||||||
|
self.perf_monitor.alert_callback = self._on_performance_alert
|
||||||
|
|
||||||
# ---------------------------------------------------------------- project loading
|
# ---------------------------------------------------------------- project loading
|
||||||
|
|
||||||
@@ -755,6 +756,17 @@ class App:
|
|||||||
"""Called from background thread when a tool call completes."""
|
"""Called from background thread when a tool call completes."""
|
||||||
session_logger.log_tool_call(script, result, None)
|
session_logger.log_tool_call(script, result, None)
|
||||||
|
|
||||||
|
def _on_performance_alert(self, message: str):
|
||||||
|
"""Called by PerformanceMonitor when a threshold is exceeded."""
|
||||||
|
alert_text = f"[PERFORMANCE ALERT] {message}. Please consider optimizing recent changes or reducing load."
|
||||||
|
# Inject into history as a 'System' message or similar
|
||||||
|
with self._pending_history_adds_lock:
|
||||||
|
self._pending_history_adds.append({
|
||||||
|
"role": "System",
|
||||||
|
"content": alert_text,
|
||||||
|
"ts": project_manager.now_ts()
|
||||||
|
})
|
||||||
|
|
||||||
def _flush_pending_comms(self):
|
def _flush_pending_comms(self):
|
||||||
"""Called every frame from the main render loop."""
|
"""Called every frame from the main render loop."""
|
||||||
with self._pending_comms_lock:
|
with self._pending_comms_lock:
|
||||||
|
|||||||
@@ -17,6 +17,16 @@ class PerformanceMonitor:
|
|||||||
self._last_input_time = None
|
self._last_input_time = None
|
||||||
self._input_lag_ms = 0.0
|
self._input_lag_ms = 0.0
|
||||||
|
|
||||||
|
# Alerts
|
||||||
|
self.alert_callback = None
|
||||||
|
self.thresholds = {
|
||||||
|
'frame_time_ms': 33.3, # < 30 FPS
|
||||||
|
'cpu_percent': 80.0,
|
||||||
|
'input_lag_ms': 100.0
|
||||||
|
}
|
||||||
|
self._last_alert_time = 0
|
||||||
|
self._alert_cooldown = 30 # seconds
|
||||||
|
|
||||||
# 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)
|
||||||
@@ -49,12 +59,35 @@ class PerformanceMonitor:
|
|||||||
self._input_lag_ms = (end_time - self._last_input_time) * 1000.0
|
self._input_lag_ms = (end_time - self._last_input_time) * 1000.0
|
||||||
self._last_input_time = None
|
self._last_input_time = None
|
||||||
|
|
||||||
|
self._check_alerts()
|
||||||
|
|
||||||
elapsed_since_fps = end_time - self._fps_last_time
|
elapsed_since_fps = end_time - self._fps_last_time
|
||||||
if elapsed_since_fps >= 1.0:
|
if elapsed_since_fps >= 1.0:
|
||||||
self._fps = self._frame_count / elapsed_since_fps
|
self._fps = self._frame_count / elapsed_since_fps
|
||||||
self._frame_count = 0
|
self._frame_count = 0
|
||||||
self._fps_last_time = end_time
|
self._fps_last_time = end_time
|
||||||
|
|
||||||
|
def _check_alerts(self):
|
||||||
|
if not self.alert_callback:
|
||||||
|
return
|
||||||
|
|
||||||
|
now = time.time()
|
||||||
|
if now - self._last_alert_time < self._alert_cooldown:
|
||||||
|
return
|
||||||
|
|
||||||
|
metrics = self.get_metrics()
|
||||||
|
alerts = []
|
||||||
|
if metrics['last_frame_time_ms'] > self.thresholds['frame_time_ms']:
|
||||||
|
alerts.append(f"Frame time high: {metrics['last_frame_time_ms']:.1f}ms")
|
||||||
|
if metrics['cpu_percent'] > self.thresholds['cpu_percent']:
|
||||||
|
alerts.append(f"CPU usage high: {metrics['cpu_percent']:.1f}%")
|
||||||
|
if metrics['input_lag_ms'] > self.thresholds['input_lag_ms']:
|
||||||
|
alerts.append(f"Input lag high: {metrics['input_lag_ms']:.1f}ms")
|
||||||
|
|
||||||
|
if alerts:
|
||||||
|
self._last_alert_time = now
|
||||||
|
self.alert_callback("; ".join(alerts))
|
||||||
|
|
||||||
def get_metrics(self):
|
def get_metrics(self):
|
||||||
with self._cpu_lock:
|
with self._cpu_lock:
|
||||||
cpu_usage = self._cpu_usage
|
cpu_usage = self._cpu_usage
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import unittest
|
import unittest
|
||||||
import time
|
import time
|
||||||
|
from unittest.mock import MagicMock
|
||||||
from performance_monitor import PerformanceMonitor
|
from performance_monitor import PerformanceMonitor
|
||||||
|
|
||||||
class TestPerformanceMonitor(unittest.TestCase):
|
class TestPerformanceMonitor(unittest.TestCase):
|
||||||
@@ -33,5 +34,18 @@ class TestPerformanceMonitor(unittest.TestCase):
|
|||||||
self.assertGreaterEqual(metrics['input_lag_ms'], 20)
|
self.assertGreaterEqual(metrics['input_lag_ms'], 20)
|
||||||
self.assertLess(metrics['input_lag_ms'], 40)
|
self.assertLess(metrics['input_lag_ms'], 40)
|
||||||
|
|
||||||
|
def test_alerts_triggering(self):
|
||||||
|
mock_callback = MagicMock()
|
||||||
|
self.monitor.alert_callback = mock_callback
|
||||||
|
self.monitor.thresholds['frame_time_ms'] = 5.0 # Low threshold
|
||||||
|
self.monitor._alert_cooldown = 0 # No cooldown for test
|
||||||
|
|
||||||
|
self.monitor.start_frame()
|
||||||
|
time.sleep(0.01) # 10ms > 5ms
|
||||||
|
self.monitor.end_frame()
|
||||||
|
|
||||||
|
mock_callback.assert_called_once()
|
||||||
|
self.assertIn("Frame time high", mock_callback.call_args[0][0])
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user