import json import argparse import matplotlib.pyplot as plt import numpy as np from datetime import datetime from matplotlib.font_manager import FontProperties # 设置中文字体支持 plt.rcParams['font.sans-serif'] = ['SimHei', 'FangSong', 'Arial Unicode MS'] plt.rcParams['axes.unicode_minus'] = False 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', default='./results/performance_analysis_report.json') parser.add_argument('--output', '-o', default='./results/performance_analysis_report.png') args = parser.parse_args() # 读取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()