Files
Test_AI/benchmark/visualizer.py
2026-01-20 10:54:30 +08:00

316 lines
11 KiB
Python

"""
Benchmark 结果可视化模块
"""
import os
from typing import List, Dict, Any
from datetime import datetime
import numpy as np
from .results import TestResult
from .utils import ensure_dir, setup_logging
logger = setup_logging()
def generate_visualizations(results: List[TestResult], output_dir: str) -> List[str]:
"""生成所有可视化图表"""
try:
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('Agg') # 非交互式后端
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
except ImportError:
logger.warning("matplotlib 未安装,跳过可视化")
return []
ensure_dir(output_dir)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
generated_files = []
# 1. GPU 利用率 vs 摄像头数量
fig_path = _plot_gpu_vs_cameras(results, output_dir, timestamp)
if fig_path:
generated_files.append(fig_path)
# 2. 吞吐量 vs Batch Size
fig_path = _plot_throughput_vs_batch(results, output_dir, timestamp)
if fig_path:
generated_files.append(fig_path)
# 3. 延迟分布
fig_path = _plot_latency_distribution(results, output_dir, timestamp)
if fig_path:
generated_files.append(fig_path)
# 4. 实时性热力图
fig_path = _plot_realtime_heatmap(results, output_dir, timestamp)
if fig_path:
generated_files.append(fig_path)
# 5. GPU 饱和点分析
fig_path = _plot_saturation_analysis(results, output_dir, timestamp)
if fig_path:
generated_files.append(fig_path)
logger.info(f"生成 {len(generated_files)} 个可视化图表")
return generated_files
def _plot_gpu_vs_cameras(results: List[TestResult], output_dir: str, timestamp: str) -> str:
"""GPU 利用率 vs 摄像头数量"""
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(10, 6))
# 按 batch size 分组
batch_sizes = sorted(set(r.batch_size for r in results))
camera_counts = sorted(set(r.num_cameras for r in results))
for bs in batch_sizes:
bs_results = [r for r in results if r.batch_size == bs]
cameras = []
gpu_utils = []
for cam in camera_counts:
cam_results = [r for r in bs_results if r.num_cameras == cam]
if cam_results:
cameras.append(cam)
gpu_utils.append(np.mean([r.gpu_utilization_avg for r in cam_results]))
if cameras:
ax.plot(cameras, gpu_utils, marker='o', label=f'Batch={bs}')
ax.axhline(y=85, color='r', linestyle='--', label='饱和阈值 (85%)')
ax.set_xlabel('摄像头数量')
ax.set_ylabel('GPU 利用率 (%)')
ax.set_title('GPU 利用率 vs 摄像头数量')
ax.legend()
ax.grid(True, alpha=0.3)
ax.set_ylim(0, 100)
fig_path = os.path.join(output_dir, f'gpu_vs_cameras_{timestamp}.png')
plt.savefig(fig_path, dpi=150, bbox_inches='tight')
plt.close()
return fig_path
def _plot_throughput_vs_batch(results: List[TestResult], output_dir: str, timestamp: str) -> str:
"""吞吐量 vs Batch Size"""
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(10, 6))
batch_sizes = sorted(set(r.batch_size for r in results))
camera_counts = sorted(set(r.num_cameras for r in results))
for cam in camera_counts:
cam_results = [r for r in results if r.num_cameras == cam]
batches = []
throughputs = []
for bs in batch_sizes:
bs_results = [r for r in cam_results if r.batch_size == bs]
if bs_results:
batches.append(bs)
throughputs.append(np.mean([r.total_throughput_fps for r in bs_results]))
if batches:
ax.plot(batches, throughputs, marker='s', label=f'{cam} 路摄像头')
ax.set_xlabel('Batch Size')
ax.set_ylabel('总吞吐量 (FPS)')
ax.set_title('吞吐量 vs Batch Size')
ax.legend()
ax.grid(True, alpha=0.3)
ax.set_xticks(batch_sizes)
fig_path = os.path.join(output_dir, f'throughput_vs_batch_{timestamp}.png')
plt.savefig(fig_path, dpi=150, bbox_inches='tight')
plt.close()
return fig_path
def _plot_latency_distribution(results: List[TestResult], output_dir: str, timestamp: str) -> str:
"""延迟分布对比"""
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
batch_sizes = sorted(set(r.batch_size for r in results))
# 左图: 平均延迟
ax1 = axes[0]
x = np.arange(len(batch_sizes))
width = 0.35
avg_latencies = [np.mean([r.avg_latency_ms for r in results if r.batch_size == bs]) for bs in batch_sizes]
p95_latencies = [np.mean([r.p95_latency_ms for r in results if r.batch_size == bs]) for bs in batch_sizes]
ax1.bar(x - width/2, avg_latencies, width, label='平均延迟')
ax1.bar(x + width/2, p95_latencies, width, label='P95 延迟')
ax1.set_xlabel('Batch Size')
ax1.set_ylabel('延迟 (ms)')
ax1.set_title('延迟 vs Batch Size')
ax1.set_xticks(x)
ax1.set_xticklabels(batch_sizes)
ax1.legend()
ax1.grid(True, alpha=0.3, axis='y')
# 右图: 按摄像头数量
ax2 = axes[1]
camera_counts = sorted(set(r.num_cameras for r in results))
avg_by_cam = [np.mean([r.avg_latency_ms for r in results if r.num_cameras == cam]) for cam in camera_counts]
p95_by_cam = [np.mean([r.p95_latency_ms for r in results if r.num_cameras == cam]) for cam in camera_counts]
x2 = np.arange(len(camera_counts))
ax2.bar(x2 - width/2, avg_by_cam, width, label='平均延迟')
ax2.bar(x2 + width/2, p95_by_cam, width, label='P95 延迟')
ax2.set_xlabel('摄像头数量')
ax2.set_ylabel('延迟 (ms)')
ax2.set_title('延迟 vs 摄像头数量')
ax2.set_xticks(x2)
ax2.set_xticklabels(camera_counts)
ax2.legend()
ax2.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
fig_path = os.path.join(output_dir, f'latency_distribution_{timestamp}.png')
plt.savefig(fig_path, dpi=150, bbox_inches='tight')
plt.close()
return fig_path
def _plot_realtime_heatmap(results: List[TestResult], output_dir: str, timestamp: str) -> str:
"""实时性热力图"""
import matplotlib.pyplot as plt
batch_sizes = sorted(set(r.batch_size for r in results))
camera_counts = sorted(set(r.num_cameras for r in results))
# 创建矩阵
matrix = np.zeros((len(camera_counts), len(batch_sizes)))
for i, cam in enumerate(camera_counts):
for j, bs in enumerate(batch_sizes):
matching = [r for r in results if r.num_cameras == cam and r.batch_size == bs]
if matching:
# 实时性比例
realtime_ratio = sum(1 for r in matching if r.is_realtime_capable) / len(matching)
matrix[i, j] = realtime_ratio * 100
fig, ax = plt.subplots(figsize=(10, 8))
im = ax.imshow(matrix, cmap='RdYlGn', aspect='auto', vmin=0, vmax=100)
ax.set_xticks(np.arange(len(batch_sizes)))
ax.set_yticks(np.arange(len(camera_counts)))
ax.set_xticklabels(batch_sizes)
ax.set_yticklabels(camera_counts)
ax.set_xlabel('Batch Size')
ax.set_ylabel('摄像头数量')
ax.set_title('实时性热力图 (绿色=可实时, 红色=不可实时)')
# 添加数值标注
for i in range(len(camera_counts)):
for j in range(len(batch_sizes)):
text = ax.text(j, i, f'{matrix[i, j]:.0f}%',
ha='center', va='center', color='black', fontsize=9)
plt.colorbar(im, ax=ax, label='实时性比例 (%)')
fig_path = os.path.join(output_dir, f'realtime_heatmap_{timestamp}.png')
plt.savefig(fig_path, dpi=150, bbox_inches='tight')
plt.close()
return fig_path
def _plot_saturation_analysis(results: List[TestResult], output_dir: str, timestamp: str) -> str:
"""GPU 饱和点分析"""
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
camera_counts = sorted(set(r.num_cameras for r in results))
batch_sizes = sorted(set(r.batch_size for r in results))
# 1. GPU 利用率 vs 吞吐量
ax1 = axes[0, 0]
for bs in batch_sizes:
bs_results = [r for r in results if r.batch_size == bs]
throughputs = [r.total_throughput_fps for r in bs_results]
gpu_utils = [r.gpu_utilization_avg for r in bs_results]
ax1.scatter(throughputs, gpu_utils, label=f'Batch={bs}', alpha=0.7)
ax1.axhline(y=85, color='r', linestyle='--', alpha=0.5)
ax1.set_xlabel('吞吐量 (FPS)')
ax1.set_ylabel('GPU 利用率 (%)')
ax1.set_title('GPU 利用率 vs 吞吐量')
ax1.legend()
ax1.grid(True, alpha=0.3)
# 2. 显存使用
ax2 = axes[0, 1]
for bs in batch_sizes:
bs_results = [r for r in results if r.batch_size == bs]
cameras = []
mem_used = []
for cam in camera_counts:
cam_results = [r for r in bs_results if r.num_cameras == cam]
if cam_results:
cameras.append(cam)
mem_used.append(np.mean([r.memory_used_mb for r in cam_results]))
if cameras:
ax2.plot(cameras, mem_used, marker='o', label=f'Batch={bs}')
ax2.set_xlabel('摄像头数量')
ax2.set_ylabel('显存使用 (MB)')
ax2.set_title('显存使用 vs 摄像头数量')
ax2.legend()
ax2.grid(True, alpha=0.3)
# 3. 丢帧率
ax3 = axes[1, 0]
for bs in batch_sizes:
bs_results = [r for r in results if r.batch_size == bs]
cameras = []
drop_rates = []
for cam in camera_counts:
cam_results = [r for r in bs_results if r.num_cameras == cam]
if cam_results:
cameras.append(cam)
drop_rates.append(np.mean([r.frame_drop_rate for r in cam_results]))
if cameras:
ax3.plot(cameras, drop_rates, marker='s', label=f'Batch={bs}')
ax3.axhline(y=5, color='r', linestyle='--', label='阈值 (5%)')
ax3.set_xlabel('摄像头数量')
ax3.set_ylabel('丢帧率 (%)')
ax3.set_title('丢帧率 vs 摄像头数量')
ax3.legend()
ax3.grid(True, alpha=0.3)
# 4. 饱和状态统计
ax4 = axes[1, 1]
saturated_count = sum(1 for r in results if r.is_gpu_saturated)
not_saturated_count = len(results) - saturated_count
ax4.pie([not_saturated_count, saturated_count],
labels=['未饱和', '已饱和'],
colors=['#2ecc71', '#e74c3c'],
autopct='%1.1f%%',
startangle=90)
ax4.set_title(f'GPU 饱和状态分布\n(共 {len(results)} 个测试)')
plt.tight_layout()
fig_path = os.path.join(output_dir, f'saturation_analysis_{timestamp}.png')
plt.savefig(fig_path, dpi=150, bbox_inches='tight')
plt.close()
return fig_path