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() # Input lag tracking self._last_input_time = None 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 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 record_input_event(self): self._last_input_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 # Calculate input lag if an input occurred during this frame if self._last_input_time is not None: self._input_lag_ms = (end_time - self._last_input_time) * 1000.0 self._last_input_time = None self._check_alerts() 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 _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): 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, 'input_lag_ms': self._input_lag_ms } def stop(self): self._stop_event.set() self._cpu_thread.join(timeout=2.0)