""" 性能监控器 监控粒子系统的性能指标 """ import time from typing import Dict, List, Optional from collections import deque from direct.task import Task class PerformanceMonitor: """ 粒子系统性能监控器 实时监控和显示性能指标 """ def __init__(self, particle_manager): """ 初始化性能监控器 Args: particle_manager: 粒子管理器 """ self.particle_manager = particle_manager self.enabled = False self.display_visible = False # 性能数据收集 self.frame_times = deque(maxlen=60) # 保存60帧的数据 self.particle_counts = deque(maxlen=60) self.render_times = deque(maxlen=60) self.physics_times = deque(maxlen=60) self.update_times = deque(maxlen=60) # 统计数据 self.stats = { 'fps': 0.0, 'avg_fps': 0.0, 'min_fps': 0.0, 'max_fps': 0.0, 'particle_count': 0, 'avg_particle_count': 0.0, 'max_particle_count': 0, 'render_time': 0.0, 'physics_time': 0.0, 'update_time': 0.0, 'memory_usage': 0.0, 'draw_calls': 0, 'vertices_rendered': 0, 'active_emitters': 0 } # 性能阈值 self.performance_thresholds = { 'low_fps_warning': 30.0, 'high_particle_warning': 5000, 'high_render_time_warning': 16.0, # ms 'high_update_time_warning': 5.0 # ms } # 警告状态 self.warnings = { 'low_fps': False, 'high_particles': False, 'high_render_time': False, 'high_update_time': False } # GUI元素 self.gui_elements = [] self.info_labels = {} # 监控任务 self.monitor_task = None # 时间记录 self.last_update_time = time.time() self.update_interval = 0.1 # 100ms更新一次 # 历史数据(用于图表显示) self.history_data = { 'fps_history': deque(maxlen=100), 'particle_count_history': deque(maxlen=100), 'render_time_history': deque(maxlen=100) } print("✓ 性能监控器初始化完成") def start(self): """启动性能监控""" if self.enabled: return self.enabled = True # 启动监控任务 if hasattr(self.particle_manager, 'world') and self.particle_manager.world: self.monitor_task = self.particle_manager.world.taskMgr.add( self._monitor_task, "particle_performance_monitor" ) print("✓ 性能监控器已启动") def stop(self): """停止性能监控""" if not self.enabled: return self.enabled = False # 停止监控任务 if self.monitor_task and hasattr(self.particle_manager, 'world'): self.particle_manager.world.taskMgr.remove(self.monitor_task) self.monitor_task = None # 隐藏显示 self.hide_display() print("✓ 性能监控器已停止") def toggle_display(self): """切换显示状态""" if self.display_visible: self.hide_display() else: self.show_display() def show_display(self): """显示性能信息""" if self.display_visible: return self.display_visible = True self._create_display_gui() print("✓ 性能监控显示已开启") def hide_display(self): """隐藏性能信息""" if not self.display_visible: return self.display_visible = False self._cleanup_display_gui() print("✓ 性能监控显示已关闭") def _monitor_task(self, task): """监控任务""" if not self.enabled: return task.done current_time = time.time() # 检查更新间隔 if current_time - self.last_update_time < self.update_interval: return task.cont dt = current_time - self.last_update_time self.last_update_time = current_time # 收集性能数据 self._collect_performance_data(dt) # 更新统计信息 self._update_statistics() # 检查性能警告 self._check_performance_warnings() # 更新显示 if self.display_visible: self._update_display() return task.cont def _collect_performance_data(self, dt: float): """收集性能数据""" try: # FPS计算 fps = 1.0 / dt if dt > 0 else 0.0 self.frame_times.append(dt) self.history_data['fps_history'].append(fps) # 获取粒子管理器统计信息 if hasattr(self.particle_manager, 'get_stats'): manager_stats = self.particle_manager.get_stats() # 粒子数量 particle_count = self.particle_manager.get_particle_count() self.particle_counts.append(particle_count) self.history_data['particle_count_history'].append(particle_count) # 渲染时间 render_time = manager_stats.get('render_time', 0.0) self.render_times.append(render_time) self.history_data['render_time_history'].append(render_time * 1000) # 转换为毫秒 # 物理时间 physics_time = manager_stats.get('physics_time', 0.0) self.physics_times.append(physics_time) # 更新时间 update_time = manager_stats.get('update_time', 0.0) self.update_times.append(update_time) # 渲染统计 if hasattr(self.particle_manager, 'renderer'): render_stats = self.particle_manager.renderer.get_render_stats() self.stats['draw_calls'] = render_stats.get('draw_calls', 0) self.stats['vertices_rendered'] = render_stats.get('vertices_rendered', 0) # 活跃发射器 self.stats['active_emitters'] = self.particle_manager.get_emitter_count() # 更新当前值 self.stats['fps'] = fps self.stats['particle_count'] = self.particle_manager.get_particle_count() if self.particle_manager else 0 except Exception as e: print(f"⚠ 性能数据收集失败: {e}") def _update_statistics(self): """更新统计信息""" try: # FPS统计 if self.frame_times: fps_values = [1.0/dt if dt > 0 else 0.0 for dt in self.frame_times] self.stats['avg_fps'] = sum(fps_values) / len(fps_values) if fps_values else 0.0 self.stats['min_fps'] = min(fps_values) if fps_values else 0.0 self.stats['max_fps'] = max(fps_values) if fps_values else 0.0 # 粒子数量统计 if self.particle_counts: self.stats['avg_particle_count'] = sum(self.particle_counts) / len(self.particle_counts) self.stats['max_particle_count'] = max(self.particle_counts) # 渲染时间统计 if self.render_times: self.stats['render_time'] = sum(self.render_times[-10:]) / min(10, len(self.render_times)) # 物理时间统计 if self.physics_times: self.stats['physics_time'] = sum(self.physics_times[-10:]) / min(10, len(self.physics_times)) # 更新时间统计 if self.update_times: self.stats['update_time'] = sum(self.update_times[-10:]) / min(10, len(self.update_times)) # 内存使用(简化估算) self.stats['memory_usage'] = self.stats['particle_count'] * 0.1 # KB per particle except Exception as e: print(f"⚠ 统计信息更新失败: {e}") def _check_performance_warnings(self): """检查性能警告""" try: # 低FPS警告 if self.stats['fps'] < self.performance_thresholds['low_fps_warning']: if not self.warnings['low_fps']: self.warnings['low_fps'] = True print(f"⚠ 性能警告: FPS过低 ({self.stats['fps']:.1f})") else: self.warnings['low_fps'] = False # 高粒子数警告 if self.stats['particle_count'] > self.performance_thresholds['high_particle_warning']: if not self.warnings['high_particles']: self.warnings['high_particles'] = True print(f"⚠ 性能警告: 粒子数过多 ({self.stats['particle_count']})") else: self.warnings['high_particles'] = False # 高渲染时间警告 render_time_ms = self.stats['render_time'] * 1000 if render_time_ms > self.performance_thresholds['high_render_time_warning']: if not self.warnings['high_render_time']: self.warnings['high_render_time'] = True print(f"⚠ 性能警告: 渲染时间过长 ({render_time_ms:.1f}ms)") else: self.warnings['high_render_time'] = False # 高更新时间警告 update_time_ms = self.stats['update_time'] * 1000 if update_time_ms > self.performance_thresholds['high_update_time_warning']: if not self.warnings['high_update_time']: self.warnings['high_update_time'] = True print(f"⚠ 性能警告: 更新时间过长 ({update_time_ms:.1f}ms)") else: self.warnings['high_update_time'] = False except Exception as e: print(f"⚠ 性能警告检查失败: {e}") def _create_display_gui(self): """创建显示GUI""" try: world = self.particle_manager.world if not hasattr(world, 'gui_manager') or not world.gui_manager: print("⚠ GUI管理器不可用,无法显示性能信息") return gui_manager = world.gui_manager # 创建性能信息面板 panel = gui_manager.createGUIButton( pos=(0.02, 0, 0.5), text="性能监控", size=0.04 ) self.gui_elements.append(panel) # 创建信息标签 info_items = [ ("FPS", "fps"), ("平均FPS", "avg_fps"), ("粒子数", "particle_count"), ("活跃发射器", "active_emitters"), ("渲染时间", "render_time"), ("物理时间", "physics_time"), ("更新时间", "update_time"), ("内存使用", "memory_usage"), ("绘制调用", "draw_calls"), ("顶点数", "vertices_rendered") ] for i, (label, key) in enumerate(info_items): # 标签 label_gui = gui_manager.createGUIButton( pos=(0.02, 0, 0.45 - i * 0.04), text=f"{label}:", size=0.03 ) self.gui_elements.append(label_gui) # 值 value_gui = gui_manager.createGUIButton( pos=(0.12, 0, 0.45 - i * 0.04), text="0", size=0.03 ) self.gui_elements.append(value_gui) self.info_labels[key] = value_gui except Exception as e: print(f"⚠ 性能显示GUI创建失败: {e}") def _cleanup_display_gui(self): """清理显示GUI""" try: world = self.particle_manager.world if hasattr(world, 'gui_manager') and world.gui_manager: gui_manager = world.gui_manager for element in self.gui_elements: gui_manager.deleteGUIElement(element) self.gui_elements.clear() self.info_labels.clear() except Exception as e: print(f"⚠ 性能显示GUI清理失败: {e}") def _update_display(self): """更新显示内容""" try: # 更新各项数值显示 for key, label_gui in self.info_labels.items(): value = self.stats.get(key, 0) if key in ['fps', 'avg_fps', 'min_fps', 'max_fps']: text = f"{value:.1f}" elif key in ['particle_count', 'max_particle_count', 'draw_calls', 'vertices_rendered', 'active_emitters']: text = f"{int(value)}" elif key in ['render_time', 'physics_time', 'update_time']: text = f"{value*1000:.1f}ms" elif key == 'memory_usage': if value > 1024: text = f"{value/1024:.1f}MB" else: text = f"{value:.1f}KB" else: text = f"{value:.2f}" label_gui.setText(text) # 根据警告状态设置颜色 color_set = False if key == 'fps' and self.warnings['low_fps']: label_gui.setColor(1, 0.3, 0.3, 1) # 红色 color_set = True elif key == 'particle_count' and self.warnings['high_particles']: label_gui.setColor(1, 0.8, 0.3, 1) # 橙色 color_set = True elif key == 'render_time' and self.warnings['high_render_time']: label_gui.setColor(1, 0.5, 0.3, 1) # 橙红色 color_set = True elif key == 'update_time' and self.warnings['high_update_time']: label_gui.setColor(1, 0.4, 0.4, 1) # 红色 color_set = True if not color_set: label_gui.setColor(1, 1, 1, 1) # 白色 except Exception as e: print(f"⚠ 性能显示更新失败: {e}") def get_performance_report(self) -> Dict: """获取性能报告""" return { 'current_stats': self.stats.copy(), 'warnings': self.warnings.copy(), 'thresholds': self.performance_thresholds.copy(), 'data_points': { 'frame_times': len(self.frame_times), 'particle_counts': len(self.particle_counts), 'render_times': len(self.render_times), 'physics_times': len(self.physics_times), 'update_times': len(self.update_times) }, 'history': { 'fps_history': list(self.history_data['fps_history']), 'particle_count_history': list(self.history_data['particle_count_history']), 'render_time_history': list(self.history_data['render_time_history']) } } def reset_statistics(self): """重置统计数据""" self.frame_times.clear() self.particle_counts.clear() self.render_times.clear() self.physics_times.clear() self.update_times.clear() # 清空历史数据 for history_list in self.history_data.values(): history_list.clear() # 重置统计值 for key in self.stats: self.stats[key] = 0.0 # 重置警告状态 for key in self.warnings: self.warnings[key] = False print("✓ 性能统计数据已重置") def set_performance_thresholds(self, **thresholds): """设置性能阈值""" for key, value in thresholds.items(): if key in self.performance_thresholds: self.performance_thresholds[key] = value print(f"✓ 性能阈值已更新 {key}: {value}") def cleanup(self): """清理监控器资源""" self.stop() self.reset_statistics() def get_history_data(self, data_type: str) -> List: """获取历史数据""" if data_type in self.history_data: return list(self.history_data[data_type]) return [] def export_performance_data(self, filepath: str) -> bool: """导出性能数据""" try: import json report = self.get_performance_report() with open(filepath, 'w', encoding='utf-8') as f: json.dump(report, f, indent=2, ensure_ascii=False) print(f"✓ 性能数据已导出到: {filepath}") return True except Exception as e: print(f"✗ 性能数据导出失败: {e}") return False