From 7fe117d3577c3d4d4fcc29e577084c4faa750379 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 23 Feb 2026 14:41:11 -0500 Subject: [PATCH] feat(perf): Implement core PerformanceMonitor for telemetry collection --- performance_monitor.py | 58 +++++++++++++++++++++++++++++++ pyproject.toml | 3 +- tests/test_performance_monitor.py | 27 ++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 performance_monitor.py create mode 100644 tests/test_performance_monitor.py diff --git a/performance_monitor.py b/performance_monitor.py new file mode 100644 index 0000000..e5874a5 --- /dev/null +++ b/performance_monitor.py @@ -0,0 +1,58 @@ +import time +import psutil +import threading + +class PerformanceMonitor: + def __init__(self): + self._start_time = None + self._last_frame_time = 0.0 + self._fps = 0.0 + self._frame_count = 0 + self._fps_last_time = time.time() + self._process = psutil.Process() + self._cpu_usage = 0.0 + self._cpu_lock = threading.Lock() + + # Start CPU usage monitoring thread + self._stop_event = threading.Event() + self._cpu_thread = threading.Thread(target=self._monitor_cpu, daemon=True) + self._cpu_thread.start() + + def _monitor_cpu(self): + while not self._stop_event.is_set(): + # psutil.cpu_percent is better than process.cpu_percent for real-time + usage = self._process.cpu_percent(interval=1.0) + with self._cpu_lock: + self._cpu_usage = usage + time.sleep(0.1) + + def start_frame(self): + self._start_time = time.time() + + def end_frame(self): + if self._start_time is None: + return + + end_time = time.time() + self._last_frame_time = (end_time - self._start_time) * 1000.0 + self._frame_count += 1 + + elapsed_since_fps = end_time - self._fps_last_time + if elapsed_since_fps >= 1.0: + self._fps = self._frame_count / elapsed_since_fps + self._frame_count = 0 + self._fps_last_time = end_time + + def get_metrics(self): + with self._cpu_lock: + cpu_usage = self._cpu_usage + + return { + 'last_frame_time_ms': self._last_frame_time, + 'fps': self._fps, + 'cpu_percent': cpu_usage + } + + def stop(self): + self._stop_event.set() + self._cpu_thread.join(timeout=2.0) diff --git a/pyproject.toml b/pyproject.toml index 4101559..9fb9e15 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,8 @@ dependencies = [ "imgui-bundle", "google-genai", "anthropic", - "tomli-w" + "tomli-w", + "psutil>=7.2.2", ] [dependency-groups] diff --git a/tests/test_performance_monitor.py b/tests/test_performance_monitor.py new file mode 100644 index 0000000..5052774 --- /dev/null +++ b/tests/test_performance_monitor.py @@ -0,0 +1,27 @@ +import unittest +import time +from performance_monitor import PerformanceMonitor + +class TestPerformanceMonitor(unittest.TestCase): + def setUp(self): + self.monitor = PerformanceMonitor() + + def test_frame_time_collection(self): + # Simulate frames for 1.1 seconds to trigger FPS calculation + start = time.time() + while time.time() - start < 1.1: + self.monitor.start_frame() + time.sleep(0.01) # ~100 FPS + self.monitor.end_frame() + + metrics = self.monitor.get_metrics() + self.assertAlmostEqual(metrics['last_frame_time_ms'], 10, delta=10) + self.assertGreater(metrics['fps'], 0) + + def test_cpu_usage_collection(self): + metrics = self.monitor.get_metrics() + self.assertIn('cpu_percent', metrics) + self.assertIsInstance(metrics['cpu_percent'], float) + +if __name__ == '__main__': + unittest.main()