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