|
|
@@ -1,13 +1,87 @@
|
|
|
import json
|
|
|
import argparse
|
|
|
+import os.path
|
|
|
+import platform
|
|
|
+import matplotlib
|
|
|
+# 设置后端以避免GUI相关问题
|
|
|
+matplotlib.use('Agg')
|
|
|
import matplotlib.pyplot as plt
|
|
|
import numpy as np
|
|
|
from datetime import datetime
|
|
|
from matplotlib.font_manager import FontProperties
|
|
|
+from matplotlib.patches import Rectangle
|
|
|
+import matplotlib.patches as mpatches
|
|
|
|
|
|
-# 设置中文字体支持
|
|
|
-plt.rcParams['font.sans-serif'] = ['SimHei', 'FangSong', 'Arial Unicode MS']
|
|
|
-plt.rcParams['axes.unicode_minus'] = False
|
|
|
+# 设置更现代化的matplotlib样式
|
|
|
+try:
|
|
|
+ plt.style.use('seaborn-v0_8-whitegrid')
|
|
|
+except:
|
|
|
+ try:
|
|
|
+ plt.style.use('seaborn-whitegrid')
|
|
|
+ except:
|
|
|
+ pass # 如果都没有,使用默认样式
|
|
|
+
|
|
|
+# 定义常量
|
|
|
+TOP_FUNCTION_COUNT = 15 # 显示的函数数量
|
|
|
+
|
|
|
+def setup_chinese_font_support():
|
|
|
+ """根据操作系统环境设置中文字体支持"""
|
|
|
+ system = platform.system().lower()
|
|
|
+
|
|
|
+ if system == 'windows':
|
|
|
+ # Windows系统字体设置
|
|
|
+ plt.rcParams['font.sans-serif'] = ['SimHei', 'FangSong', 'Arial Unicode MS']
|
|
|
+ plt.rcParams['axes.unicode_minus'] = False
|
|
|
+
|
|
|
+ elif system == 'linux':
|
|
|
+ # 检查是否在WSL环境中
|
|
|
+ try:
|
|
|
+ with open('/proc/version', 'r') as f:
|
|
|
+ version_info = f.read().lower()
|
|
|
+ is_wsl = 'microsoft' in version_info or 'wsl' in version_info
|
|
|
+ except:
|
|
|
+ is_wsl = False
|
|
|
+
|
|
|
+ if is_wsl:
|
|
|
+ # WSL环境下的字体设置
|
|
|
+ # 尝试使用Windows字体目录中的字体
|
|
|
+ possible_fonts = [
|
|
|
+ '/mnt/c/Windows/Fonts/msyh.ttc', # 微软雅黑
|
|
|
+ '/mnt/c/Windows/Fonts/simsun.ttc', # 宋体
|
|
|
+ '/mnt/c/Windows/Fonts/simhei.ttf', # 黑体
|
|
|
+ ]
|
|
|
+
|
|
|
+ # 查找可用的中文字体
|
|
|
+ available_fonts = []
|
|
|
+ for font_path in possible_fonts:
|
|
|
+ if os.path.exists(font_path):
|
|
|
+ available_fonts.append(font_path)
|
|
|
+
|
|
|
+ # 如果找到了Windows字体,使用它
|
|
|
+ if available_fonts:
|
|
|
+ plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimSun', 'SimHei']
|
|
|
+ else:
|
|
|
+ # 使用系统默认字体
|
|
|
+ plt.rcParams['font.sans-serif'] = ['DejaVu Sans', 'Bitstream Vera Sans', 'Lucida Grande', 'Verdana', 'Geneva', 'Lucid', 'Arial', 'Helvetica', 'sans-serif']
|
|
|
+ else:
|
|
|
+ # 普通Linux环境
|
|
|
+ plt.rcParams['font.sans-serif'] = ['WenQuanYi Micro Hei', 'DejaVu Sans', 'Bitstream Vera Sans', 'Lucida Grande', 'Verdana', 'Geneva', 'Lucid', 'Arial', 'Helvetica', 'sans-serif']
|
|
|
+
|
|
|
+ plt.rcParams['axes.unicode_minus'] = False
|
|
|
+ else:
|
|
|
+ # 其他系统使用默认设置
|
|
|
+ plt.rcParams['axes.unicode_minus'] = False
|
|
|
+
|
|
|
+# 在导入后立即设置字体支持
|
|
|
+setup_chinese_font_support()
|
|
|
+
|
|
|
+# 设置全局样式参数
|
|
|
+plt.rcParams['figure.facecolor'] = 'white'
|
|
|
+plt.rcParams['axes.facecolor'] = '#fafafa'
|
|
|
+plt.rcParams['axes.edgecolor'] = '#e0e0e0'
|
|
|
+plt.rcParams['axes.linewidth'] = 0.8
|
|
|
+plt.rcParams['grid.alpha'] = 0.5
|
|
|
+plt.rcParams['grid.color'] = '#e0e0e0'
|
|
|
|
|
|
def read_json(file_path):
|
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
|
@@ -17,12 +91,13 @@ def read_json(file_path):
|
|
|
def plot_cprofile_table(ax, cprofile_data):
|
|
|
"""绘制cProfile详细信息表格"""
|
|
|
if not cprofile_data or 'points' not in cprofile_data:
|
|
|
- ax.text(0.5, 0.5, '无cProfile数据', ha='center', va='center', transform=ax.transAxes)
|
|
|
- ax.set_title('函数运行详细信息')
|
|
|
+ ax.text(0.5, 0.5, '无cProfile数据', ha='center', va='center', transform=ax.transAxes,
|
|
|
+ fontsize=12, color='#666666')
|
|
|
+ ax.set_title('函数运行详细信息', fontsize=14, fontweight='bold', pad=20)
|
|
|
return
|
|
|
|
|
|
# 只取前15个最耗时的函数
|
|
|
- fun_num = 15
|
|
|
+ fun_num = TOP_FUNCTION_COUNT
|
|
|
points = cprofile_data['points'][:fun_num]
|
|
|
|
|
|
# 准备表格数据
|
|
|
@@ -45,20 +120,30 @@ def plot_cprofile_table(ax, cprofile_data):
|
|
|
|
|
|
# 创建表格
|
|
|
columns = ['函数名称', '调用次数', '总时间(s)', '累计时间(s)']
|
|
|
- table = ax.table(cellText=table_data, colLabels=columns, cellLoc='center', loc='center')
|
|
|
+ table = ax.table(cellText=table_data, colLabels=columns, cellLoc='center', loc='center',
|
|
|
+ bbox=[0, 0, 1, 1])
|
|
|
table.auto_set_font_size(False)
|
|
|
table.set_fontsize(9)
|
|
|
table.scale(1, 1.5)
|
|
|
|
|
|
# 设置表格样式
|
|
|
for i in range(len(columns)):
|
|
|
- table[(0, i)].set_facecolor('#4CAF50')
|
|
|
+ table[(0, i)].set_facecolor('#4a86e8')
|
|
|
table[(0, i)].set_text_props(weight='bold', color='white')
|
|
|
+
|
|
|
+ # 为数据行设置交替颜色
|
|
|
+ for i in range(1, len(table_data)+1):
|
|
|
+ for j in range(len(columns)):
|
|
|
+ if i % 2 == 0:
|
|
|
+ table[(i, j)].set_facecolor('#f2f2f2')
|
|
|
+ else:
|
|
|
+ table[(i, j)].set_facecolor('white')
|
|
|
+
|
|
|
# 根据函数数量动态调整标题距离
|
|
|
# 每个函数行大约需要8个单位的高度,加上基础padding
|
|
|
pad_value = 8 + len(points) * 3
|
|
|
- ax.set_title(f'函数运行详细信息 (Top {fun_num} 函数)', fontsize=14, pad=pad_value)
|
|
|
- # 坐标轴掩藏
|
|
|
+ ax.set_title(f'函数运行详细信息 (Top {fun_num} 函数)', fontsize=14, fontweight='bold', pad=pad_value)
|
|
|
+ # 坐标轴隐藏
|
|
|
ax.set_xticks([])
|
|
|
ax.set_yticks([])
|
|
|
ax.spines['top'].set_visible(False)
|
|
|
@@ -70,14 +155,22 @@ def plot_cprofile_table(ax, cprofile_data):
|
|
|
def plot_cprofile_bar_chart(ax, cprofile_data):
|
|
|
"""绘制cProfile总时间柱状图"""
|
|
|
if not cprofile_data or 'points' not in cprofile_data:
|
|
|
- ax.text(0.5, 0.5, '无cProfile数据', ha='center', va='center', transform=ax.transAxes)
|
|
|
- ax.set_title('函数运行总时间')
|
|
|
+ ax.text(0.5, 0.5, '无cProfile数据', ha='center', va='center', transform=ax.transAxes,
|
|
|
+ fontsize=12, color='#666666')
|
|
|
+ ax.set_title('函数运行总时间', fontsize=14, fontweight='bold', pad=20)
|
|
|
return
|
|
|
|
|
|
# 只取前N个最耗时的函数
|
|
|
- fun_num = 15
|
|
|
+ fun_num = TOP_FUNCTION_COUNT
|
|
|
points = cprofile_data['points'][:fun_num]
|
|
|
|
|
|
+ # 如果没有数据点,直接返回
|
|
|
+ if not points:
|
|
|
+ ax.text(0.5, 0.5, '无cProfile数据', ha='center', va='center', transform=ax.transAxes,
|
|
|
+ fontsize=12, color='#666666')
|
|
|
+ ax.set_title('函数运行总时间', fontsize=14, fontweight='bold', pad=20)
|
|
|
+ return
|
|
|
+
|
|
|
# 提取函数名和总时间
|
|
|
functions = []
|
|
|
total_times = []
|
|
|
@@ -93,42 +186,59 @@ def plot_cprofile_bar_chart(ax, cprofile_data):
|
|
|
functions.append(func_name_display)
|
|
|
total_times.append(point['total_time'])
|
|
|
|
|
|
- # 创建垂直柱状图,使用渐变色彩
|
|
|
- x_pos = np.arange(len(functions))
|
|
|
- colors = plt.cm.viridis(np.linspace(0, 1, len(functions)))
|
|
|
- bars = ax.bar(x_pos, total_times, color=colors)
|
|
|
+ # 创建水平柱状图,使用渐变色彩,更适合长函数名显示
|
|
|
+ y_pos = np.arange(len(functions))
|
|
|
+ colors = plt.cm.plasma(np.linspace(0.1, 0.9, len(functions))) # 使用plasma色彩映射
|
|
|
+ bars = ax.barh(y_pos, total_times, color=colors, height=0.8, edgecolor='white', linewidth=0.5)
|
|
|
|
|
|
# 设置坐标轴
|
|
|
- ax.set_xticks(x_pos)
|
|
|
- ax.set_xticklabels(functions, rotation=30, ha='right', fontsize=9)
|
|
|
- ax.set_ylabel('总时间 (秒)', fontsize=12)
|
|
|
- ax.set_title(f'函数运行总时间 (Top {fun_num} 函数)', fontsize=14, pad=20)
|
|
|
+ ax.set_yticks(y_pos)
|
|
|
+ ax.set_yticklabels(functions, fontsize=9)
|
|
|
+ ax.set_xlabel('总时间 (秒)', fontsize=12, fontweight='bold')
|
|
|
+ ax.set_title(f'函数运行总时间 (Top {fun_num} 函数)', fontsize=14, fontweight='bold', pad=20)
|
|
|
|
|
|
# 改善网格线样式
|
|
|
- ax.grid(axis='y', alpha=0.7, linestyle='--', linewidth=0.5)
|
|
|
+ ax.grid(axis='x', alpha=0.4, linestyle='-', linewidth=0.5)
|
|
|
ax.set_axisbelow(True)
|
|
|
|
|
|
# 在柱状图上添加数值标签,优化显示效果
|
|
|
- for bar, time in zip(bars, total_times):
|
|
|
- height = bar.get_height()
|
|
|
- ax.text(bar.get_x() + bar.get_width()/2., height,
|
|
|
- f'{time:.6f}', ha='center', va='bottom', fontsize=8,
|
|
|
- rotation=0, weight='bold')
|
|
|
+ for i, (bar, time) in enumerate(zip(bars, total_times)):
|
|
|
+ width = bar.get_width()
|
|
|
+ ax.text(width + max(total_times)*0.01, bar.get_y() + bar.get_height()/2.,
|
|
|
+ f'{time:.6f}', ha='left', va='center', fontsize=8,
|
|
|
+ fontweight='bold', color='#333333')
|
|
|
+
|
|
|
+ # 添加颜色条以显示颜色映射含义
|
|
|
+ if total_times: # 确保有数据才添加颜色条
|
|
|
+ sm = plt.cm.ScalarMappable(cmap=plt.cm.plasma, norm=plt.Normalize(vmin=min(total_times), vmax=max(total_times)))
|
|
|
+ sm.set_array([])
|
|
|
+ cbar = plt.colorbar(sm, ax=ax, shrink=0.8, aspect=20, pad=0.02)
|
|
|
+ cbar.set_label('执行时间 (秒)', fontsize=10, fontweight='bold')
|
|
|
|
|
|
# 添加边框
|
|
|
for spine in ax.spines.values():
|
|
|
- spine.set_linewidth(0.5)
|
|
|
+ spine.set_linewidth(0.8)
|
|
|
+ spine.set_color('#cccccc')
|
|
|
+
|
|
|
|
|
|
def plot_memory_data(ax, memory_data):
|
|
|
"""绘制内存使用数据"""
|
|
|
if not memory_data or 'points' not in memory_data:
|
|
|
- ax.text(0.5, 0.5, '无内存数据', ha='center', va='center', transform=ax.transAxes)
|
|
|
- ax.set_title('memory_profiler 内存使用情况')
|
|
|
+ ax.text(0.5, 0.5, '无内存数据', ha='center', va='center', transform=ax.transAxes,
|
|
|
+ fontsize=12, color='#666666')
|
|
|
+ ax.set_title('memory_profiler 内存使用情况', fontsize=14, fontweight='bold', pad=20)
|
|
|
return
|
|
|
|
|
|
timestamps = [point['timestamp'] for point in memory_data['points']]
|
|
|
memory_values = [point['memory'] for point in memory_data['points']]
|
|
|
|
|
|
+ # 检查是否有数据
|
|
|
+ if not timestamps or not memory_values:
|
|
|
+ ax.text(0.5, 0.5, '无内存数据', ha='center', va='center', transform=ax.transAxes,
|
|
|
+ fontsize=12, color='#666666')
|
|
|
+ ax.set_title('memory_profiler 内存使用情况', fontsize=14, fontweight='bold', pad=20)
|
|
|
+ return
|
|
|
+
|
|
|
# 转换时间戳为相对时间(秒)
|
|
|
start_time = timestamps[0] if timestamps else 0
|
|
|
relative_times = [t - start_time for t in timestamps]
|
|
|
@@ -138,30 +248,52 @@ def plot_memory_data(ax, memory_data):
|
|
|
max_index = memory_values.index(max_memory) if memory_values else 0
|
|
|
max_time = relative_times[max_index] if relative_times else 0
|
|
|
|
|
|
- # 绘制内存使用情况曲线
|
|
|
- line, = ax.plot(relative_times, memory_values, marker='o', markersize=3, linewidth=1.5)
|
|
|
+ # 计算平均内存使用量
|
|
|
+ avg_memory = np.mean(memory_values) if memory_values else 0
|
|
|
+
|
|
|
+ # 绘制内存使用情况曲线,使用更平滑的线条
|
|
|
+ ax.plot(relative_times, memory_values, color='#2e7d32', linewidth=2.5, marker='o',
|
|
|
+ markersize=5, markerfacecolor='#4caf50', markeredgecolor='white', markeredgewidth=1.5,
|
|
|
+ markevery=slice(0, len(relative_times), max(1, len(relative_times)//20))) # 控制标记密度
|
|
|
|
|
|
# 在峰值点添加特殊标记
|
|
|
- ax.plot(max_time, max_memory, marker='o', markersize=8, color='red',
|
|
|
- markerfacecolor='none', markeredgewidth=2, label=f'峰值: {max_memory:.2f} MB')
|
|
|
-
|
|
|
- ax.set_xlabel('时间 (秒)')
|
|
|
- ax.set_ylabel('内存使用量 (MB)')
|
|
|
- ax.set_title(f'进程内存使用情况 (峰值: {max_memory:.2f} MB at {max_time:.1f}s)')
|
|
|
- ax.grid(True, alpha=0.3)
|
|
|
- ax.legend()
|
|
|
+ ax.scatter(max_time, max_memory, s=150, color='#d32f2f', marker='o',
|
|
|
+ edgecolor='white', linewidth=2, zorder=5, label=f'峰值: {max_memory:.2f} MB')
|
|
|
+
|
|
|
+ # 添加平均线
|
|
|
+ ax.axhline(y=avg_memory, color='#1976d2', linestyle='--', linewidth=1.5,
|
|
|
+ label=f'平均: {avg_memory:.2f} MB', alpha=0.8)
|
|
|
+
|
|
|
+ # 添加填充区域以增强视觉效果
|
|
|
+ ax.fill_between(relative_times, memory_values, alpha=0.4, color='#4caf50')
|
|
|
+
|
|
|
+ # 添加统计信息文本框
|
|
|
+ textstr = f'峰值: {max_memory:.2f} MB\n平均: {avg_memory:.2f} MB'
|
|
|
+ props = dict(boxstyle='round', facecolor='#e3f2fd', alpha=0.8)
|
|
|
+ ax.text(0.03, 0.97, textstr, transform=ax.transAxes, fontsize=10,
|
|
|
+ verticalalignment='top', bbox=props, fontweight='bold')
|
|
|
+
|
|
|
+ ax.set_xlabel('时间 (秒)', fontsize=12, fontweight='bold')
|
|
|
+ ax.set_ylabel('内存使用量 (MB)', fontsize=12, fontweight='bold')
|
|
|
+ ax.set_title(f'进程内存使用情况 (峰值: {max_memory:.2f} MB at {max_time:.1f}s)',
|
|
|
+ fontsize=14, fontweight='bold', pad=20)
|
|
|
+ ax.grid(True, alpha=0.4)
|
|
|
+ ax.legend(loc='upper right', frameon=True, fancybox=True, shadow=True, ncol=1)
|
|
|
+
|
|
|
|
|
|
def plot_psutil_data(ax, psutil_data):
|
|
|
"""绘制psutil系统资源数据"""
|
|
|
if not psutil_data or 'points' not in psutil_data:
|
|
|
- ax.text(0.5, 0.5, '无进程资源数据', ha='center', va='center', transform=ax.transAxes)
|
|
|
- ax.set_title('进程资源使用情况')
|
|
|
+ ax.text(0.5, 0.5, '无进程资源数据', ha='center', va='center', transform=ax.transAxes,
|
|
|
+ fontsize=12, color='#666666')
|
|
|
+ ax.set_title('进程资源使用情况', fontsize=14, fontweight='bold', pad=20)
|
|
|
return
|
|
|
|
|
|
points = psutil_data['points']
|
|
|
if not points:
|
|
|
- ax.text(0.5, 0.5, '无进程资源数据', ha='center', va='center', transform=ax.transAxes)
|
|
|
- ax.set_title('进程资源使用情况')
|
|
|
+ ax.text(0.5, 0.5, '无进程资源数据', ha='center', va='center', transform=ax.transAxes,
|
|
|
+ fontsize=12, color='#666666')
|
|
|
+ ax.set_title('进程资源使用情况', fontsize=14, fontweight='bold', pad=20)
|
|
|
return
|
|
|
|
|
|
# 提取数据并转换时间字符串为时间戳
|
|
|
@@ -213,42 +345,98 @@ def plot_psutil_data(ax, psutil_data):
|
|
|
except ValueError:
|
|
|
mem_pct_values.append(0)
|
|
|
|
|
|
+ # 检查是否有有效数据
|
|
|
+ if not any([rss_values, vms_values, cpu_values, mem_pct_values]):
|
|
|
+ ax.text(0.5, 0.5, '无有效进程资源数据', ha='center', va='center', transform=ax.transAxes,
|
|
|
+ fontsize=12, color='#666666')
|
|
|
+ ax.set_title('进程资源使用情况', fontsize=14, fontweight='bold', pad=20)
|
|
|
+ return
|
|
|
+
|
|
|
+ # 计算统计数据
|
|
|
+ avg_cpu = np.mean(cpu_values) if cpu_values else 0
|
|
|
+ max_cpu = max(cpu_values) if cpu_values else 0
|
|
|
+ avg_rss = np.mean(rss_values) if rss_values else 0
|
|
|
+ max_rss = max(rss_values) if rss_values else 0
|
|
|
+
|
|
|
# 绘制双轴图
|
|
|
ax2 = ax.twinx()
|
|
|
|
|
|
+ lines = [] # 存储线条引用
|
|
|
+
|
|
|
# 物理内存使用量(蓝色)
|
|
|
- line1, = ax.plot(relative_times, rss_values, marker='o', color='blue', label='物理内存(RSS)')
|
|
|
+ if rss_values:
|
|
|
+ line1, = ax.plot(relative_times, rss_values, marker='o', markersize=5, linewidth=2.5,
|
|
|
+ color='#1976d2', label='物理内存(RSS)', markevery=slice(0, len(relative_times), max(1, len(relative_times)//15)))
|
|
|
+ lines.append(line1)
|
|
|
+ # 添加平均线
|
|
|
+ ax.axhline(y=avg_rss, color='#1976d2', linestyle=':', linewidth=1.5, alpha=0.7,
|
|
|
+ label=f'平均RSS: {avg_rss:.1f} MB')
|
|
|
+
|
|
|
# 虚拟内存使用量(绿色)
|
|
|
- line3, = ax.plot(relative_times, vms_values, marker='^', color='green', label='虚拟内存(VMS)')
|
|
|
- ax.set_xlabel('时间 (秒)')
|
|
|
- ax.set_ylabel('内存使用量 (MB)', color='blue')
|
|
|
- ax.tick_params(axis='y', labelcolor='blue')
|
|
|
+ if vms_values:
|
|
|
+ line3, = ax.plot(relative_times, vms_values, marker='^', markersize=6, linewidth=2.5,
|
|
|
+ color='#388e3c', label='虚拟内存(VMS)', markevery=slice(0, len(relative_times), max(1, len(relative_times)//15)))
|
|
|
+ lines.append(line3)
|
|
|
+
|
|
|
+ ax.set_xlabel('时间 (秒)', fontsize=12, fontweight='bold')
|
|
|
+ ax.set_ylabel('内存使用量 (MB)', fontsize=12, fontweight='bold', color='#1976d2')
|
|
|
+ ax.tick_params(axis='y', labelcolor='#1976d2')
|
|
|
+
|
|
|
+ line2 = None
|
|
|
+ line4 = None
|
|
|
|
|
|
# CPU使用率(红色)和内存占用率(紫色)
|
|
|
- line2, = ax2.plot(relative_times, cpu_values, marker='s', color='red', label='CPU使用率')
|
|
|
- line4, = ax2.plot(relative_times, mem_pct_values, marker='d', color='purple', label='内存占用率')
|
|
|
- ax2.set_ylabel('百分比 (%)', color='red')
|
|
|
- ax2.tick_params(axis='y', labelcolor='red')
|
|
|
+ if cpu_values:
|
|
|
+ line2, = ax2.plot(relative_times, cpu_values, marker='s', markersize=5, linewidth=2.5,
|
|
|
+ color='#d32f2f', label='CPU使用率', markevery=slice(0, len(relative_times), max(1, len(relative_times)//15)))
|
|
|
+ lines.append(line2)
|
|
|
+ # 添加CPU平均线和峰值标记
|
|
|
+ ax2.axhline(y=avg_cpu, color='#d32f2f', linestyle=':', linewidth=1.5, alpha=0.7,
|
|
|
+ label=f'平均CPU: {avg_cpu:.1f}%')
|
|
|
+
|
|
|
+ # 寻找CPU峰值点并标记
|
|
|
+ if cpu_values:
|
|
|
+ max_cpu_idx = cpu_values.index(max_cpu)
|
|
|
+ max_cpu_time = relative_times[max_cpu_idx]
|
|
|
+ ax2.scatter(max_cpu_time, max_cpu, s=120, color='#d32f2f', marker='*',
|
|
|
+ edgecolor='white', linewidth=1, zorder=5)
|
|
|
|
|
|
- # 合并图例
|
|
|
- lines = [line1, line3, line2, line4]
|
|
|
- labels = [l.get_label() for l in lines]
|
|
|
- ax.legend(lines, labels, loc='upper left')
|
|
|
+ if mem_pct_values:
|
|
|
+ line4, = ax2.plot(relative_times, mem_pct_values, marker='d', markersize=6, linewidth=2.5,
|
|
|
+ color='#7b1fa2', label='内存占用率', markevery=slice(0, len(relative_times), max(1, len(relative_times)//15)))
|
|
|
+ lines.append(line4)
|
|
|
+
|
|
|
+ ax2.set_ylabel('百分比 (%)', fontsize=12, fontweight='bold', color='#d32f2f')
|
|
|
+ ax2.tick_params(axis='y', labelcolor='#d32f2f')
|
|
|
|
|
|
- ax.set_title('进程资源使用情况')
|
|
|
- ax.grid(True, alpha=0.3)
|
|
|
+ # 合并图例
|
|
|
+ if lines:
|
|
|
+ labels = [l.get_label() for l in lines]
|
|
|
+ ax.legend(lines, labels, loc='upper left', frameon=True, fancybox=True, shadow=True, ncol=2)
|
|
|
+
|
|
|
+ # 添加统计信息文本框
|
|
|
+ textstr = f'CPU峰值: {max_cpu:.1f}%\n内存峰值: {max_rss:.1f} MB'
|
|
|
+ props = dict(boxstyle='round', facecolor='#ffebee', alpha=0.8)
|
|
|
+ ax.text(0.03, 0.97, textstr, transform=ax.transAxes, fontsize=10,
|
|
|
+ verticalalignment='top', bbox=props, fontweight='bold')
|
|
|
+
|
|
|
+ ax.set_title('进程资源使用情况', fontsize=14, fontweight='bold', pad=20)
|
|
|
+ ax.grid(True, alpha=0.4)
|
|
|
+
|
|
|
|
|
|
def plot_network_data(ax, psutil_data):
|
|
|
"""绘制网络使用数据"""
|
|
|
if not psutil_data or 'points' not in psutil_data:
|
|
|
- ax.text(0.5, 0.5, '无网络数据', ha='center', va='center', transform=ax.transAxes)
|
|
|
- ax.set_title('系统网络使用情况')
|
|
|
+ ax.text(0.5, 0.5, '无网络数据', ha='center', va='center', transform=ax.transAxes,
|
|
|
+ fontsize=12, color='#666666')
|
|
|
+ ax.set_title('系统网络使用情况', fontsize=14, fontweight='bold', pad=20)
|
|
|
return
|
|
|
|
|
|
points = psutil_data['points']
|
|
|
if not points:
|
|
|
- ax.text(0.5, 0.5, '无网络数据', ha='center', va='center', transform=ax.transAxes)
|
|
|
- ax.set_title('系统网络使用情况')
|
|
|
+ ax.text(0.5, 0.5, '无网络数据', ha='center', va='center', transform=ax.transAxes,
|
|
|
+ fontsize=12, color='#666666')
|
|
|
+ ax.set_title('系统网络使用情况', fontsize=14, fontweight='bold', pad=20)
|
|
|
return
|
|
|
|
|
|
# 提取数据并转换时间字符串为时间戳
|
|
|
@@ -285,27 +473,62 @@ def plot_network_data(ax, psutil_data):
|
|
|
except ValueError:
|
|
|
net_send_values.append(0)
|
|
|
|
|
|
- # 绘制网络使用情况
|
|
|
- ax.plot(relative_times, net_recv_values, marker='o', color='blue', label='网络接收')
|
|
|
- ax.plot(relative_times, net_send_values, marker='s', color='red', label='网络发送')
|
|
|
+ # 检查是否有有效数据
|
|
|
+ if not net_recv_values and not net_send_values:
|
|
|
+ ax.text(0.5, 0.5, '无有效网络数据', ha='center', va='center', transform=ax.transAxes,
|
|
|
+ fontsize=12, color='#666666')
|
|
|
+ ax.set_title('系统网络使用情况', fontsize=14, fontweight='bold', pad=20)
|
|
|
+ return
|
|
|
|
|
|
- ax.set_xlabel('时间 (秒)')
|
|
|
- ax.set_ylabel('速率 (MB/s)')
|
|
|
- ax.set_title('系统网络使用情况')
|
|
|
- ax.grid(True, alpha=0.3)
|
|
|
- ax.legend()
|
|
|
+ # 计算统计数据
|
|
|
+ avg_recv = np.mean(net_recv_values) if net_recv_values else 0
|
|
|
+ avg_send = np.mean(net_send_values) if net_send_values else 0
|
|
|
+ max_recv = max(net_recv_values) if net_recv_values else 0
|
|
|
+ max_send = max(net_send_values) if net_send_values else 0
|
|
|
+
|
|
|
+ # 绘制网络使用情况,使用面积图增强视觉效果
|
|
|
+ if net_recv_values:
|
|
|
+ ax.plot(relative_times, net_recv_values, marker='o', markersize=5, linewidth=2.5,
|
|
|
+ color='#1976d2', label='网络接收', markevery=slice(0, len(relative_times), max(1, len(relative_times)//15)))
|
|
|
+ ax.fill_between(relative_times, net_recv_values, alpha=0.3, color='#1976d2')
|
|
|
+ # 添加平均线
|
|
|
+ ax.axhline(y=avg_recv, color='#1976d2', linestyle=':', linewidth=1.5, alpha=0.7,
|
|
|
+ label=f'平均接收: {avg_recv:.2f} MB/s')
|
|
|
+
|
|
|
+ if net_send_values:
|
|
|
+ ax.plot(relative_times, net_send_values, marker='s', markersize=5, linewidth=2.5,
|
|
|
+ color='#ff9800', label='网络发送', markevery=slice(0, len(relative_times), max(1, len(relative_times)//15)))
|
|
|
+ ax.fill_between(relative_times, net_send_values, alpha=0.3, color='#ff9800')
|
|
|
+ # 添加平均线
|
|
|
+ ax.axhline(y=avg_send, color='#ff9800', linestyle=':', linewidth=1.5, alpha=0.7,
|
|
|
+ label=f'平均发送: {avg_send:.2f} MB/s')
|
|
|
+
|
|
|
+ # 添加统计信息文本框
|
|
|
+ textstr = f'接收峰值: {max_recv:.2f} MB/s\n发送峰值: {max_send:.2f} MB/s'
|
|
|
+ props = dict(boxstyle='round', facecolor='#e3f2fd', alpha=0.8)
|
|
|
+ ax.text(0.03, 0.97, textstr, transform=ax.transAxes, fontsize=10,
|
|
|
+ verticalalignment='top', bbox=props, fontweight='bold')
|
|
|
+
|
|
|
+ ax.set_xlabel('时间 (秒)', fontsize=12, fontweight='bold')
|
|
|
+ ax.set_ylabel('速率 (MB/s)', fontsize=12, fontweight='bold')
|
|
|
+ ax.set_title('系统网络使用情况', fontsize=14, fontweight='bold', pad=20)
|
|
|
+ ax.grid(True, alpha=0.4)
|
|
|
+ ax.legend(loc='upper right', frameon=True, fancybox=True, shadow=True, ncol=1)
|
|
|
+
|
|
|
|
|
|
def plot_disk_io_data(ax, psutil_data):
|
|
|
"""绘制进程IO数据"""
|
|
|
if not psutil_data or 'points' not in psutil_data:
|
|
|
- ax.text(0.5, 0.5, '无进程IO数据', ha='center', va='center', transform=ax.transAxes)
|
|
|
- ax.set_title('进程IO情况')
|
|
|
+ ax.text(0.5, 0.5, '无进程IO数据', ha='center', va='center', transform=ax.transAxes,
|
|
|
+ fontsize=12, color='#666666')
|
|
|
+ ax.set_title('进程IO情况', fontsize=14, fontweight='bold', pad=20)
|
|
|
return
|
|
|
|
|
|
points = psutil_data['points']
|
|
|
if not points:
|
|
|
- ax.text(0.5, 0.5, '无进程IO数据', ha='center', va='center', transform=ax.transAxes)
|
|
|
- ax.set_title('进程IO情况')
|
|
|
+ ax.text(0.5, 0.5, '无进程IO数据', ha='center', va='center', transform=ax.transAxes,
|
|
|
+ fontsize=12, color='#666666')
|
|
|
+ ax.set_title('进程IO情况', fontsize=14, fontweight='bold', pad=20)
|
|
|
return
|
|
|
|
|
|
# 提取数据并转换时间字符串为时间戳
|
|
|
@@ -341,28 +564,70 @@ def plot_disk_io_data(ax, psutil_data):
|
|
|
except ValueError:
|
|
|
disk_write_values.append(0)
|
|
|
|
|
|
- # 绘制磁盘IO情况
|
|
|
- ax.plot(relative_times, disk_read_values, marker='o', color='blue', label='磁盘读取')
|
|
|
- ax.plot(relative_times, disk_write_values, marker='s', color='red', label='磁盘写入')
|
|
|
+ # 检查是否有有效数据
|
|
|
+ if not disk_read_values and not disk_write_values:
|
|
|
+ ax.text(0.5, 0.5, '无有效IO数据', ha='center', va='center', transform=ax.transAxes,
|
|
|
+ fontsize=12, color='#666666')
|
|
|
+ ax.set_title('进程IO情况', fontsize=14, fontweight='bold', pad=20)
|
|
|
+ return
|
|
|
|
|
|
- ax.set_xlabel('时间 (秒)')
|
|
|
- ax.set_ylabel('速率 (MB/s)')
|
|
|
- ax.set_title('进程IO情况')
|
|
|
- ax.grid(True, alpha=0.3)
|
|
|
- ax.legend()
|
|
|
+ # 计算统计数据
|
|
|
+ avg_read = np.mean(disk_read_values) if disk_read_values else 0
|
|
|
+ avg_write = np.mean(disk_write_values) if disk_write_values else 0
|
|
|
+ max_read = max(disk_read_values) if disk_read_values else 0
|
|
|
+ max_write = max(disk_write_values) if disk_write_values else 0
|
|
|
+
|
|
|
+ # 绘制磁盘IO情况,使用不同样式的线条区分读写操作
|
|
|
+ if disk_read_values:
|
|
|
+ ax.plot(relative_times, disk_read_values, marker='o', markersize=5, linewidth=2.5,
|
|
|
+ color='#7b1fa2', label='磁盘读取', linestyle='-', markevery=slice(0, len(relative_times), max(1, len(relative_times)//15)))
|
|
|
+ ax.fill_between(relative_times, disk_read_values, alpha=0.3, color='#7b1fa2')
|
|
|
+ # 添加平均线
|
|
|
+ ax.axhline(y=avg_read, color='#7b1fa2', linestyle=':', linewidth=1.5, alpha=0.7,
|
|
|
+ label=f'平均读取: {avg_read:.2f} MB/s')
|
|
|
+
|
|
|
+ if disk_write_values:
|
|
|
+ ax.plot(relative_times, disk_write_values, marker='s', markersize=5, linewidth=2.5,
|
|
|
+ color='#f57c00', label='磁盘写入', linestyle='--', markevery=slice(0, len(relative_times), max(1, len(relative_times)//15)))
|
|
|
+ ax.fill_between(relative_times, disk_write_values, alpha=0.3, color='#f57c00')
|
|
|
+ # 添加平均线
|
|
|
+ ax.axhline(y=avg_write, color='#f57c00', linestyle=':', linewidth=1.5, alpha=0.7,
|
|
|
+ label=f'平均写入: {avg_write:.2f} MB/s')
|
|
|
+
|
|
|
+ # 添加统计信息文本框
|
|
|
+ textstr = f'读取峰值: {max_read:.2f} MB/s\n写入峰值: {max_write:.2f} MB/s'
|
|
|
+ props = dict(boxstyle='round', facecolor='#f3e5f5', alpha=0.8)
|
|
|
+ ax.text(0.03, 0.97, textstr, transform=ax.transAxes, fontsize=10,
|
|
|
+ verticalalignment='top', bbox=props, fontweight='bold')
|
|
|
+
|
|
|
+ ax.set_xlabel('时间 (秒)', fontsize=12, fontweight='bold')
|
|
|
+ ax.set_ylabel('速率 (MB/s)', fontsize=12, fontweight='bold')
|
|
|
+ ax.set_title('进程IO情况', fontsize=14, fontweight='bold', pad=20)
|
|
|
+ ax.grid(True, alpha=0.4)
|
|
|
+ ax.legend(loc='upper right', frameon=True, fancybox=True, shadow=True, ncol=1)
|
|
|
|
|
|
def main():
|
|
|
parser = argparse.ArgumentParser(description='绘制性能分析结果图')
|
|
|
+ # parser.add_argument('--input','-i', required=True)
|
|
|
+ # parser.add_argument('--output', '-o',required=True)
|
|
|
parser.add_argument('--input','-i', default='./results/performance_analysis_report.json')
|
|
|
parser.add_argument('--output', '-o', default='./results/performance_analysis_report.png')
|
|
|
args = parser.parse_args()
|
|
|
-
|
|
|
+ if not os.path.exists(args.input):
|
|
|
+ raise ValueError(f'输入文件不存在:{args.input}')
|
|
|
# 读取json数据
|
|
|
data = read_json(args.input)
|
|
|
|
|
|
# 创建更大的图表
|
|
|
fig = plt.figure(figsize=(20, 18))
|
|
|
- fig.suptitle('Python 性能分析报告', fontsize=20, fontweight='bold')
|
|
|
+ fig.suptitle('Python 性能分析报告', fontsize=24, fontweight='bold', y=0.95)
|
|
|
+
|
|
|
+ # 设置图表背景色
|
|
|
+ fig.patch.set_facecolor('white')
|
|
|
+
|
|
|
+ # 添加水印效果
|
|
|
+ fig.text(0.5, 0.5, 'Python Performance Report', fontsize=40, color='gray',
|
|
|
+ ha='center', va='center', alpha=0.05, rotation=45)
|
|
|
|
|
|
# 创建子图
|
|
|
# cProfile 表格
|
|
|
@@ -387,11 +652,19 @@ def main():
|
|
|
plot_disk_io_data(ax6, data.get('analysis_results', {}).get('psutil', {}))
|
|
|
|
|
|
# 调整布局
|
|
|
- plt.tight_layout()
|
|
|
+ plt.tight_layout(rect=[0, 0.01, 1, 0.93]) # 减少底部边距以减少空白区域
|
|
|
+
|
|
|
+ # 添加页脚信息
|
|
|
+ fig.text(0.95, 0.005, f'生成时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}',
|
|
|
+ fontsize=10, ha='right', va='bottom', color='#666666')
|
|
|
+ fig.text(0.05, 0.005, 'Python性能分析工具', fontsize=10, ha='left', va='bottom', color='#666666')
|
|
|
|
|
|
# 保存图像
|
|
|
- plt.savefig(args.output, dpi=300, bbox_inches='tight')
|
|
|
+ plt.savefig(args.output, dpi=300, bbox_inches='tight', facecolor='white',
|
|
|
+ edgecolor='none', orientation='landscape')
|
|
|
print(f"性能分析图表已保存至: {args.output}")
|
|
|
+ plt.close() # 关闭图形以释放内存
|
|
|
+ # plt.show() # 可选:显示图表
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
- main()
|
|
|
+ main()
|