EG/plugins/user/particle_system/tools/performance_monitor.py
2025-12-12 16:16:15 +08:00

484 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
性能监控器
监控粒子系统的性能指标
"""
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