| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673 |
- 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
- # 设置更现代化的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:
- 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,
- fontsize=12, color='#666666')
- ax.set_title('函数运行详细信息', fontsize=14, fontweight='bold', pad=20)
- return
-
- # 只取前15个最耗时的函数
- fun_num = TOP_FUNCTION_COUNT
- 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',
- 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('#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, fontweight='bold', 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,
- fontsize=12, color='#666666')
- ax.set_title('函数运行总时间', fontsize=14, fontweight='bold', pad=20)
- return
-
- # 只取前N个最耗时的函数
- 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 = []
-
- 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'])
-
- # 创建水平柱状图,使用渐变色彩,更适合长函数名显示
- 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_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='x', alpha=0.4, linestyle='-', linewidth=0.5)
- ax.set_axisbelow(True)
-
- # 在柱状图上添加数值标签,优化显示效果
- 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.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,
- 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]
-
- # 查找内存峰值及其位置
- 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
-
- # 计算平均内存使用量
- 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.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,
- 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,
- fontsize=12, color='#666666')
- ax.set_title('进程资源使用情况', fontsize=14, fontweight='bold', pad=20)
- 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)
-
- # 检查是否有有效数据
- 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 = [] # 存储线条引用
-
- # 物理内存使用量(蓝色)
- 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')
-
- # 虚拟内存使用量(绿色)
- 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使用率(红色)和内存占用率(紫色)
- 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)
-
- 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')
-
- # 添加统计信息文本框
- 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')
-
- # 合并图例,放置在右侧避免与统计信息重叠
- if lines:
- labels = [l.get_label() for l in lines]
- # 将图例放在左上角,但在统计信息文本框的下方
- ax.legend(lines, labels, loc='upper left', frameon=True, fancybox=True,
- shadow=True, ncol=1, bbox_to_anchor=(0.02, 0.85)) # 调整bbox_to_anchor来控制具体位置
- 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,
- 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,
- fontsize=12, color='#666666')
- ax.set_title('系统网络使用情况', fontsize=14, fontweight='bold', pad=20)
- 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)
-
- # 检查是否有有效数据
- 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
-
- # 计算统计数据
- 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,
- 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,
- fontsize=12, color='#666666')
- ax.set_title('进程IO情况', fontsize=14, fontweight='bold', pad=20)
- 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)
-
- # 检查是否有有效数据
- 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
-
- # 计算统计数据
- 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=(24, 16))
- 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 表格
- ax1 = plt.subplot2grid((10, 2), (0, 0), rowspan=3, colspan=1)
- # cProfile 柱状图
- ax2 = plt.subplot2grid((10, 2), (0, 1), rowspan=3,colspan=1)
- # 内存使用情况
- ax3 = plt.subplot2grid((10, 2), (3, 0), rowspan=4,colspan=1)
- # 系统资源监控
- ax4 = plt.subplot2grid((10, 2), (3, 1), rowspan=4,colspan=1)
- # 网络使用情况
- ax5 = plt.subplot2grid((10, 2), (7, 0), rowspan=3, colspan=1)
- # 磁盘读写情况
- ax6 = plt.subplot2grid((10, 2), (7, 1), rowspan=3, 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(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', facecolor='white',
- edgecolor='none', orientation='landscape')
- print(f"性能分析图表已保存至: {args.output}")
- plt.close() # 关闭图形以释放内存
- # plt.show() # 可选:显示图表
- if __name__ == '__main__':
- main()
|