|
|
@@ -1,416 +0,0 @@
|
|
|
-import json
|
|
|
-import argparse
|
|
|
-import os.path
|
|
|
-import platform
|
|
|
-import matplotlib
|
|
|
-import matplotlib.pyplot as plt
|
|
|
-import numpy as np
|
|
|
-from datetime import datetime
|
|
|
-from matplotlib.font_manager import FontProperties
|
|
|
-
|
|
|
-def setup_chinese_font_support():
|
|
|
- """根据操作系统环境设置中文字体支持"""
|
|
|
- system = platform.system().lower()
|
|
|
-
|
|
|
- if system == 'windows':
|
|
|
- # Windows系统字体设置
|
|
|
- plt.rcParams['font.sans-serif'] = ['SimHei', 'FangSong', 'Arial Unicode MS']
|
|
|
-
|
|
|
- elif system == 'linux':
|
|
|
- # 检查是否在WSL环境中
|
|
|
- plt.rcParams['font.sans-serif'] = ['WenQuanYi Micro Hei', 'Microsoft YaHei', 'SimSun']
|
|
|
- plt.rcParams['axes.unicode_minus'] = False
|
|
|
-
|
|
|
-
|
|
|
-# 在导入后立即设置字体支持
|
|
|
-setup_chinese_font_support()
|
|
|
-
|
|
|
-def read_json(file_path):
|
|
|
- with open(file_path, 'r', encoding='utf-8') as f:
|
|
|
- data = json.load(f)
|
|
|
- return data
|
|
|
-
|
|
|
-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('函数运行详细信息')
|
|
|
- return
|
|
|
-
|
|
|
- # 只取前15个最耗时的函数
|
|
|
- fun_num = 15
|
|
|
- points = cprofile_data['points'][:fun_num]
|
|
|
-
|
|
|
- # 准备表格数据
|
|
|
- table_data = []
|
|
|
- for point in points:
|
|
|
- # 简化函数名显示
|
|
|
- func_name = point['function'][1:-1]
|
|
|
- func_name = func_name.split(',')[-1].strip()
|
|
|
- func_name = func_name[1:-1]
|
|
|
-
|
|
|
- # 限制函数名长度,但保留足够空间显示完整信息
|
|
|
- func_name_display = func_name[:35] + '...' if len(func_name) > 35 else func_name
|
|
|
- # 将数据点添加到表格
|
|
|
- table_data.append([
|
|
|
- func_name_display,
|
|
|
- point['call_count'],
|
|
|
- f"{point['total_time']:.6f}",
|
|
|
- f"{point['cumulative_time']:.6f}"
|
|
|
- ])
|
|
|
-
|
|
|
- # 创建表格
|
|
|
- columns = ['函数名称', '调用次数', '总时间(s)', '累计时间(s)']
|
|
|
- table = ax.table(cellText=table_data, colLabels=columns, cellLoc='center', loc='center')
|
|
|
- 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_text_props(weight='bold', color='white')
|
|
|
- # 根据函数数量动态调整标题距离
|
|
|
- # 每个函数行大约需要8个单位的高度,加上基础padding
|
|
|
- pad_value = 8 + len(points) * 3
|
|
|
- ax.set_title(f'函数运行详细信息 (Top {fun_num} 函数)', fontsize=14, pad=pad_value)
|
|
|
- # 坐标轴掩藏
|
|
|
- ax.set_xticks([])
|
|
|
- ax.set_yticks([])
|
|
|
- ax.spines['top'].set_visible(False)
|
|
|
- ax.spines['right'].set_visible(False)
|
|
|
- ax.spines['bottom'].set_visible(False)
|
|
|
- ax.spines['left'].set_visible(False)
|
|
|
-
|
|
|
-
|
|
|
-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('函数运行总时间')
|
|
|
- return
|
|
|
-
|
|
|
- # 只取前N个最耗时的函数
|
|
|
- fun_num = 15
|
|
|
- points = cprofile_data['points'][:fun_num]
|
|
|
-
|
|
|
- # 提取函数名和总时间
|
|
|
- functions = []
|
|
|
- total_times = []
|
|
|
-
|
|
|
- for point in points:
|
|
|
- # 简化函数名显示
|
|
|
- func_name = point['function'][1:-1]
|
|
|
- func_name = func_name.split(',')[-1].strip()
|
|
|
- func_name = func_name[1:-1]
|
|
|
-
|
|
|
- # 限制函数名长度
|
|
|
- func_name_display = func_name[:35] + '...' if len(func_name) > 35 else func_name
|
|
|
- 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)
|
|
|
-
|
|
|
- # 设置坐标轴
|
|
|
- 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.grid(axis='y', alpha=0.7, 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 spine in ax.spines.values():
|
|
|
- spine.set_linewidth(0.5)
|
|
|
-
|
|
|
-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 内存使用情况')
|
|
|
- return
|
|
|
-
|
|
|
- timestamps = [point['timestamp'] for point in memory_data['points']]
|
|
|
- memory_values = [point['memory'] for point in memory_data['points']]
|
|
|
-
|
|
|
- # 转换时间戳为相对时间(秒)
|
|
|
- start_time = timestamps[0] if timestamps else 0
|
|
|
- relative_times = [t - start_time for t in timestamps]
|
|
|
-
|
|
|
- # 查找内存峰值及其位置
|
|
|
- max_memory = max(memory_values) if memory_values else 0
|
|
|
- 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)
|
|
|
-
|
|
|
- # 在峰值点添加特殊标记
|
|
|
- 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()
|
|
|
-
|
|
|
-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('进程资源使用情况')
|
|
|
- return
|
|
|
-
|
|
|
- points = psutil_data['points']
|
|
|
- if not points:
|
|
|
- ax.text(0.5, 0.5, '无进程资源数据', ha='center', va='center', transform=ax.transAxes)
|
|
|
- ax.set_title('进程资源使用情况')
|
|
|
- return
|
|
|
-
|
|
|
- # 提取数据并转换时间字符串为时间戳
|
|
|
- timestamps = []
|
|
|
- rss_values = []
|
|
|
- vms_values = []
|
|
|
- cpu_values = []
|
|
|
- mem_pct_values = []
|
|
|
-
|
|
|
- for point in points:
|
|
|
- # 解析时间字符串并转换为时间戳
|
|
|
- try:
|
|
|
- dt = datetime.strptime(point['now_time'], '%Y-%m-%d %H:%M:%S')
|
|
|
- timestamps.append(dt.timestamp())
|
|
|
- except ValueError:
|
|
|
- # 如果解析失败,使用索引作为后备
|
|
|
- timestamps.append(len(timestamps))
|
|
|
-
|
|
|
- # 转换时间戳为相对时间(秒)
|
|
|
- start_time = timestamps[0] if timestamps else 0
|
|
|
- relative_times = [t - start_time for t in timestamps]
|
|
|
-
|
|
|
- for point in points:
|
|
|
- # 解析RSS内存值
|
|
|
- rss_str = point['rss'].replace('MB', '')
|
|
|
- try:
|
|
|
- rss_values.append(float(rss_str))
|
|
|
- except ValueError:
|
|
|
- rss_values.append(0)
|
|
|
-
|
|
|
- # 解析VMS内存值
|
|
|
- vms_str = point['vms'].replace('MB', '')
|
|
|
- try:
|
|
|
- vms_values.append(float(vms_str))
|
|
|
- except ValueError:
|
|
|
- vms_values.append(0)
|
|
|
-
|
|
|
- # 解析CPU百分比
|
|
|
- cpu_str = point['cpu_pct'].replace('%', '')
|
|
|
- try:
|
|
|
- cpu_values.append(float(cpu_str))
|
|
|
- except ValueError:
|
|
|
- cpu_values.append(0)
|
|
|
-
|
|
|
- # 解析内存占用率
|
|
|
- mem_pct_str = point['mem_pct'].replace('%', '')
|
|
|
- try:
|
|
|
- mem_pct_values.append(float(mem_pct_str))
|
|
|
- except ValueError:
|
|
|
- mem_pct_values.append(0)
|
|
|
-
|
|
|
- # 绘制双轴图
|
|
|
- ax2 = ax.twinx()
|
|
|
-
|
|
|
- # 物理内存使用量(蓝色)
|
|
|
- line1, = ax.plot(relative_times, rss_values, marker='o', color='blue', label='物理内存(RSS)')
|
|
|
- # 虚拟内存使用量(绿色)
|
|
|
- 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')
|
|
|
-
|
|
|
- # 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')
|
|
|
-
|
|
|
- # 合并图例
|
|
|
- lines = [line1, line3, line2, line4]
|
|
|
- labels = [l.get_label() for l in lines]
|
|
|
- ax.legend(lines, labels, loc='upper left')
|
|
|
-
|
|
|
- ax.set_title('进程资源使用情况')
|
|
|
- ax.grid(True, alpha=0.3)
|
|
|
-
|
|
|
-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('系统网络使用情况')
|
|
|
- return
|
|
|
-
|
|
|
- points = psutil_data['points']
|
|
|
- if not points:
|
|
|
- ax.text(0.5, 0.5, '无网络数据', ha='center', va='center', transform=ax.transAxes)
|
|
|
- ax.set_title('系统网络使用情况')
|
|
|
- return
|
|
|
-
|
|
|
- # 提取数据并转换时间字符串为时间戳
|
|
|
- from datetime import datetime
|
|
|
- timestamps = []
|
|
|
- net_recv_values = []
|
|
|
- net_send_values = []
|
|
|
-
|
|
|
- for point in points:
|
|
|
- # 解析时间字符串并转换为时间戳
|
|
|
- try:
|
|
|
- dt = datetime.strptime(point['now_time'], '%Y-%m-%d %H:%M:%S')
|
|
|
- timestamps.append(dt.timestamp())
|
|
|
- except ValueError:
|
|
|
- # 如果解析失败,使用索引作为后备
|
|
|
- timestamps.append(len(timestamps))
|
|
|
-
|
|
|
- # 转换为相对时间(秒)
|
|
|
- start_time = timestamps[0] if timestamps else 0
|
|
|
- relative_times = [t - start_time for t in timestamps]
|
|
|
-
|
|
|
- for point in points:
|
|
|
- # 解析网络接收值
|
|
|
- net_recv_str = point['sys_net_recv'].replace('MB/s', '').replace('MB', '').replace(' ', '')
|
|
|
- try:
|
|
|
- net_recv_values.append(float(net_recv_str))
|
|
|
- except ValueError:
|
|
|
- net_recv_values.append(0)
|
|
|
-
|
|
|
- # 解析网络发送值
|
|
|
- net_send_str = point['sys_net_send'].replace('MB/s', '').replace('MB', '').replace(' ', '')
|
|
|
- try:
|
|
|
- net_send_values.append(float(net_send_str))
|
|
|
- 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='网络发送')
|
|
|
-
|
|
|
- ax.set_xlabel('时间 (秒)')
|
|
|
- ax.set_ylabel('速率 (MB/s)')
|
|
|
- ax.set_title('系统网络使用情况')
|
|
|
- ax.grid(True, alpha=0.3)
|
|
|
- ax.legend()
|
|
|
-
|
|
|
-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情况')
|
|
|
- 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情况')
|
|
|
- return
|
|
|
-
|
|
|
- # 提取数据并转换时间字符串为时间戳
|
|
|
- timestamps = []
|
|
|
- disk_read_values = []
|
|
|
- disk_write_values = []
|
|
|
-
|
|
|
- for point in points:
|
|
|
- # 解析时间字符串并转换为时间戳
|
|
|
- try:
|
|
|
- dt = datetime.strptime(point['now_time'], '%Y-%m-%d %H:%M:%S')
|
|
|
- timestamps.append(dt.timestamp())
|
|
|
- except ValueError:
|
|
|
- # 如果解析失败,使用索引作为后备
|
|
|
- timestamps.append(len(timestamps))
|
|
|
-
|
|
|
- # 转换为相对时间(秒)
|
|
|
- start_time = timestamps[0] if timestamps else 0
|
|
|
- relative_times = [t - start_time for t in timestamps]
|
|
|
-
|
|
|
- for point in points:
|
|
|
- # 解析磁盘读取值
|
|
|
- disk_read_str = point['disk_read'].replace('MB/s', '').replace('MB', '').replace(' ', '')
|
|
|
- try:
|
|
|
- disk_read_values.append(float(disk_read_str))
|
|
|
- except ValueError:
|
|
|
- disk_read_values.append(0)
|
|
|
-
|
|
|
- # 解析磁盘写入值
|
|
|
- disk_write_str = point['disk_write'].replace('MB/s', '').replace('MB', '').replace(' ', '')
|
|
|
- try:
|
|
|
- disk_write_values.append(float(disk_write_str))
|
|
|
- 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='磁盘写入')
|
|
|
-
|
|
|
- ax.set_xlabel('时间 (秒)')
|
|
|
- ax.set_ylabel('速率 (MB/s)')
|
|
|
- ax.set_title('进程IO情况')
|
|
|
- ax.grid(True, alpha=0.3)
|
|
|
- ax.legend()
|
|
|
-
|
|
|
-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')
|
|
|
-
|
|
|
- # 创建子图
|
|
|
- # cProfile 表格
|
|
|
- ax1 = plt.subplot2grid((4, 2), (0, 0), colspan=1)
|
|
|
- # cProfile 柱状图
|
|
|
- ax2 = plt.subplot2grid((4, 2), (0, 1), colspan=1)
|
|
|
- # 内存使用情况
|
|
|
- ax3 = plt.subplot2grid((4, 2), (1, 0), colspan=1)
|
|
|
- # 系统资源监控
|
|
|
- ax4 = plt.subplot2grid((4, 2), (1, 1), colspan=1)
|
|
|
- # 网络使用情况
|
|
|
- ax5 = plt.subplot2grid((4, 2), (2, 0), colspan=1)
|
|
|
- # 磁盘读写情况
|
|
|
- ax6 = plt.subplot2grid((4, 2), (2, 1), colspan=1)
|
|
|
-
|
|
|
- # 绘制各个子图
|
|
|
- plot_cprofile_table(ax1, data.get('analysis_results', {}).get('cprofile', {}))
|
|
|
- plot_cprofile_bar_chart(ax2, data.get('analysis_results', {}).get('cprofile', {}))
|
|
|
- plot_memory_data(ax3, data.get('analysis_results', {}).get('memory_profile', {}))
|
|
|
- plot_psutil_data(ax4, data.get('analysis_results', {}).get('psutil', {}))
|
|
|
- plot_network_data(ax5, data.get('analysis_results', {}).get('psutil', {}))
|
|
|
- plot_disk_io_data(ax6, data.get('analysis_results', {}).get('psutil', {}))
|
|
|
-
|
|
|
- # 调整布局
|
|
|
- plt.tight_layout()
|
|
|
-
|
|
|
- # 保存图像
|
|
|
- plt.savefig(args.output, dpi=300, bbox_inches='tight')
|
|
|
- print(f"性能分析图表已保存至: {args.output}")
|
|
|
-
|
|
|
-if __name__ == '__main__':
|
|
|
- main()
|