plot.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. import json
  2. import argparse
  3. import os.path
  4. import platform
  5. import matplotlib
  6. # 设置后端以避免GUI相关问题
  7. matplotlib.use('Agg')
  8. import matplotlib.pyplot as plt
  9. import numpy as np
  10. from datetime import datetime
  11. from matplotlib.font_manager import FontProperties
  12. from matplotlib.patches import Rectangle
  13. import matplotlib.patches as mpatches
  14. # 设置更现代化的matplotlib样式
  15. try:
  16. plt.style.use('seaborn-v0_8-whitegrid')
  17. except:
  18. try:
  19. plt.style.use('seaborn-whitegrid')
  20. except:
  21. pass # 如果都没有,使用默认样式
  22. # 定义常量
  23. TOP_FUNCTION_COUNT = 15 # 显示的函数数量
  24. def setup_chinese_font_support():
  25. """根据操作系统环境设置中文字体支持"""
  26. system = platform.system().lower()
  27. if system == 'windows':
  28. # Windows系统字体设置
  29. plt.rcParams['font.sans-serif'] = ['SimHei', 'FangSong', 'Arial Unicode MS']
  30. plt.rcParams['axes.unicode_minus'] = False
  31. elif system == 'linux':
  32. # 检查是否在WSL环境中
  33. try:
  34. with open('/proc/version', 'r') as f:
  35. version_info = f.read().lower()
  36. is_wsl = 'microsoft' in version_info or 'wsl' in version_info
  37. except:
  38. is_wsl = False
  39. if is_wsl:
  40. # WSL环境下的字体设置
  41. # 尝试使用Windows字体目录中的字体
  42. possible_fonts = [
  43. '/mnt/c/Windows/Fonts/msyh.ttc', # 微软雅黑
  44. '/mnt/c/Windows/Fonts/simsun.ttc', # 宋体
  45. '/mnt/c/Windows/Fonts/simhei.ttf', # 黑体
  46. ]
  47. # 查找可用的中文字体
  48. available_fonts = []
  49. for font_path in possible_fonts:
  50. if os.path.exists(font_path):
  51. available_fonts.append(font_path)
  52. # 如果找到了Windows字体,使用它
  53. if available_fonts:
  54. plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimSun', 'SimHei']
  55. else:
  56. # 使用系统默认字体
  57. plt.rcParams['font.sans-serif'] = ['DejaVu Sans', 'Bitstream Vera Sans', 'Lucida Grande', 'Verdana', 'Geneva', 'Lucid', 'Arial', 'Helvetica', 'sans-serif']
  58. else:
  59. # 普通Linux环境
  60. plt.rcParams['font.sans-serif'] = ['WenQuanYi Micro Hei', 'DejaVu Sans', 'Bitstream Vera Sans', 'Lucida Grande', 'Verdana', 'Geneva', 'Lucid', 'Arial', 'Helvetica', 'sans-serif']
  61. plt.rcParams['axes.unicode_minus'] = False
  62. else:
  63. # 其他系统使用默认设置
  64. plt.rcParams['axes.unicode_minus'] = False
  65. # 在导入后立即设置字体支持
  66. setup_chinese_font_support()
  67. # 设置全局样式参数
  68. plt.rcParams['figure.facecolor'] = 'white'
  69. plt.rcParams['axes.facecolor'] = '#fafafa'
  70. plt.rcParams['axes.edgecolor'] = '#e0e0e0'
  71. plt.rcParams['axes.linewidth'] = 0.8
  72. plt.rcParams['grid.alpha'] = 0.5
  73. plt.rcParams['grid.color'] = '#e0e0e0'
  74. def read_json(file_path):
  75. with open(file_path, 'r', encoding='utf-8') as f:
  76. data = json.load(f)
  77. return data
  78. def plot_cprofile_table(ax, cprofile_data):
  79. """绘制cProfile详细信息表格"""
  80. if not cprofile_data or 'points' not in cprofile_data:
  81. ax.text(0.5, 0.5, '无cProfile数据', ha='center', va='center', transform=ax.transAxes,
  82. fontsize=12, color='#666666')
  83. ax.set_title('函数运行详细信息', fontsize=14, fontweight='bold', pad=20)
  84. return
  85. # 只取前15个最耗时的函数
  86. fun_num = TOP_FUNCTION_COUNT
  87. points = cprofile_data['points'][:fun_num]
  88. # 准备表格数据
  89. table_data = []
  90. for point in points:
  91. # 简化函数名显示
  92. func_name = point['function'][1:-1]
  93. func_name = func_name.split(',')[-1].strip()
  94. func_name = func_name[1:-1]
  95. # 限制函数名长度,但保留足够空间显示完整信息
  96. func_name_display = func_name[:35] + '...' if len(func_name) > 35 else func_name
  97. # 将数据点添加到表格
  98. table_data.append([
  99. func_name_display,
  100. point['call_count'],
  101. f"{point['total_time']:.6f}",
  102. f"{point['cumulative_time']:.6f}"
  103. ])
  104. # 创建表格
  105. columns = ['函数名称', '调用次数', '总时间(s)', '累计时间(s)']
  106. table = ax.table(cellText=table_data, colLabels=columns, cellLoc='center', loc='center',
  107. bbox=[0, 0, 1, 1])
  108. table.auto_set_font_size(False)
  109. table.set_fontsize(9)
  110. table.scale(1, 1.5)
  111. # 设置表格样式
  112. for i in range(len(columns)):
  113. table[(0, i)].set_facecolor('#4a86e8')
  114. table[(0, i)].set_text_props(weight='bold', color='white')
  115. # 为数据行设置交替颜色
  116. for i in range(1, len(table_data)+1):
  117. for j in range(len(columns)):
  118. if i % 2 == 0:
  119. table[(i, j)].set_facecolor('#f2f2f2')
  120. else:
  121. table[(i, j)].set_facecolor('white')
  122. # 根据函数数量动态调整标题距离
  123. # 每个函数行大约需要8个单位的高度,加上基础padding
  124. pad_value = 8 + len(points) * 3
  125. ax.set_title(f'函数运行详细信息 (Top {fun_num} 函数)', fontsize=14, fontweight='bold', pad=pad_value)
  126. # 坐标轴隐藏
  127. ax.set_xticks([])
  128. ax.set_yticks([])
  129. ax.spines['top'].set_visible(False)
  130. ax.spines['right'].set_visible(False)
  131. ax.spines['bottom'].set_visible(False)
  132. ax.spines['left'].set_visible(False)
  133. def plot_cprofile_bar_chart(ax, cprofile_data):
  134. """绘制cProfile总时间柱状图"""
  135. if not cprofile_data or 'points' not in cprofile_data:
  136. ax.text(0.5, 0.5, '无cProfile数据', ha='center', va='center', transform=ax.transAxes,
  137. fontsize=12, color='#666666')
  138. ax.set_title('函数运行总时间', fontsize=14, fontweight='bold', pad=20)
  139. return
  140. # 只取前N个最耗时的函数
  141. fun_num = TOP_FUNCTION_COUNT
  142. points = cprofile_data['points'][:fun_num]
  143. # 如果没有数据点,直接返回
  144. if not points:
  145. ax.text(0.5, 0.5, '无cProfile数据', ha='center', va='center', transform=ax.transAxes,
  146. fontsize=12, color='#666666')
  147. ax.set_title('函数运行总时间', fontsize=14, fontweight='bold', pad=20)
  148. return
  149. # 提取函数名和总时间
  150. functions = []
  151. total_times = []
  152. for point in points:
  153. # 简化函数名显示
  154. func_name = point['function'][1:-1]
  155. func_name = func_name.split(',')[-1].strip()
  156. func_name = func_name[1:-1]
  157. # 限制函数名长度
  158. func_name_display = func_name[:35] + '...' if len(func_name) > 35 else func_name
  159. functions.append(func_name_display)
  160. total_times.append(point['total_time'])
  161. # 创建水平柱状图,使用渐变色彩,更适合长函数名显示
  162. y_pos = np.arange(len(functions))
  163. colors = plt.cm.plasma(np.linspace(0.1, 0.9, len(functions))) # 使用plasma色彩映射
  164. bars = ax.barh(y_pos, total_times, color=colors, height=0.8, edgecolor='white', linewidth=0.5)
  165. # 设置坐标轴
  166. ax.set_yticks(y_pos)
  167. ax.set_yticklabels(functions, fontsize=9)
  168. ax.set_xlabel('总时间 (秒)', fontsize=12, fontweight='bold')
  169. ax.set_title(f'函数运行总时间 (Top {fun_num} 函数)', fontsize=14, fontweight='bold', pad=20)
  170. # 改善网格线样式
  171. ax.grid(axis='x', alpha=0.4, linestyle='-', linewidth=0.5)
  172. ax.set_axisbelow(True)
  173. # 在柱状图上添加数值标签,优化显示效果
  174. for i, (bar, time) in enumerate(zip(bars, total_times)):
  175. width = bar.get_width()
  176. ax.text(width + max(total_times)*0.01, bar.get_y() + bar.get_height()/2.,
  177. f'{time:.6f}', ha='left', va='center', fontsize=8,
  178. fontweight='bold', color='#333333')
  179. # 添加颜色条以显示颜色映射含义
  180. if total_times: # 确保有数据才添加颜色条
  181. sm = plt.cm.ScalarMappable(cmap=plt.cm.plasma, norm=plt.Normalize(vmin=min(total_times), vmax=max(total_times)))
  182. sm.set_array([])
  183. cbar = plt.colorbar(sm, ax=ax, shrink=0.8, aspect=20, pad=0.02)
  184. cbar.set_label('执行时间 (秒)', fontsize=10, fontweight='bold')
  185. # 添加边框
  186. for spine in ax.spines.values():
  187. spine.set_linewidth(0.8)
  188. spine.set_color('#cccccc')
  189. def plot_memory_data(ax, memory_data):
  190. """绘制内存使用数据"""
  191. if not memory_data or 'points' not in memory_data:
  192. ax.text(0.5, 0.5, '无内存数据', ha='center', va='center', transform=ax.transAxes,
  193. fontsize=12, color='#666666')
  194. ax.set_title('memory_profiler 内存使用情况', fontsize=14, fontweight='bold', pad=20)
  195. return
  196. timestamps = [point['timestamp'] for point in memory_data['points']]
  197. memory_values = [point['memory'] for point in memory_data['points']]
  198. # 检查是否有数据
  199. if not timestamps or not memory_values:
  200. ax.text(0.5, 0.5, '无内存数据', ha='center', va='center', transform=ax.transAxes,
  201. fontsize=12, color='#666666')
  202. ax.set_title('memory_profiler 内存使用情况', fontsize=14, fontweight='bold', pad=20)
  203. return
  204. # 转换时间戳为相对时间(秒)
  205. start_time = timestamps[0] if timestamps else 0
  206. relative_times = [t - start_time for t in timestamps]
  207. # 查找内存峰值及其位置
  208. max_memory = max(memory_values) if memory_values else 0
  209. max_index = memory_values.index(max_memory) if memory_values else 0
  210. max_time = relative_times[max_index] if relative_times else 0
  211. # 计算平均内存使用量
  212. avg_memory = np.mean(memory_values) if memory_values else 0
  213. # 绘制内存使用情况曲线,使用更平滑的线条
  214. ax.plot(relative_times, memory_values, color='#2e7d32', linewidth=2.5, marker='o',
  215. markersize=5, markerfacecolor='#4caf50', markeredgecolor='white', markeredgewidth=1.5,
  216. markevery=slice(0, len(relative_times), max(1, len(relative_times)//20))) # 控制标记密度
  217. # 在峰值点添加特殊标记
  218. ax.scatter(max_time, max_memory, s=150, color='#d32f2f', marker='o',
  219. edgecolor='white', linewidth=2, zorder=5, label=f'峰值: {max_memory:.2f} MB')
  220. # 添加平均线
  221. ax.axhline(y=avg_memory, color='#1976d2', linestyle='--', linewidth=1.5,
  222. label=f'平均: {avg_memory:.2f} MB', alpha=0.8)
  223. # 添加填充区域以增强视觉效果
  224. ax.fill_between(relative_times, memory_values, alpha=0.4, color='#4caf50')
  225. # 添加统计信息文本框
  226. textstr = f'峰值: {max_memory:.2f} MB\n平均: {avg_memory:.2f} MB'
  227. props = dict(boxstyle='round', facecolor='#e3f2fd', alpha=0.8)
  228. ax.text(0.03, 0.97, textstr, transform=ax.transAxes, fontsize=10,
  229. verticalalignment='top', bbox=props, fontweight='bold')
  230. ax.set_xlabel('时间 (秒)', fontsize=12, fontweight='bold')
  231. ax.set_ylabel('内存使用量 (MB)', fontsize=12, fontweight='bold')
  232. ax.set_title(f'进程内存使用情况 (峰值: {max_memory:.2f} MB at {max_time:.1f}s)',
  233. fontsize=14, fontweight='bold', pad=20)
  234. ax.grid(True, alpha=0.4)
  235. ax.legend(loc='upper right', frameon=True, fancybox=True, shadow=True, ncol=1)
  236. def plot_psutil_data(ax, psutil_data):
  237. """绘制psutil系统资源数据"""
  238. if not psutil_data or 'points' not in psutil_data:
  239. ax.text(0.5, 0.5, '无进程资源数据', ha='center', va='center', transform=ax.transAxes,
  240. fontsize=12, color='#666666')
  241. ax.set_title('进程资源使用情况', fontsize=14, fontweight='bold', pad=20)
  242. return
  243. points = psutil_data['points']
  244. if not points:
  245. ax.text(0.5, 0.5, '无进程资源数据', ha='center', va='center', transform=ax.transAxes,
  246. fontsize=12, color='#666666')
  247. ax.set_title('进程资源使用情况', fontsize=14, fontweight='bold', pad=20)
  248. return
  249. # 提取数据并转换时间字符串为时间戳
  250. timestamps = []
  251. rss_values = []
  252. vms_values = []
  253. cpu_values = []
  254. mem_pct_values = []
  255. for point in points:
  256. # 解析时间字符串并转换为时间戳
  257. try:
  258. dt = datetime.strptime(point['now_time'], '%Y-%m-%d %H:%M:%S')
  259. timestamps.append(dt.timestamp())
  260. except ValueError:
  261. # 如果解析失败,使用索引作为后备
  262. timestamps.append(len(timestamps))
  263. # 转换时间戳为相对时间(秒)
  264. start_time = timestamps[0] if timestamps else 0
  265. relative_times = [t - start_time for t in timestamps]
  266. for point in points:
  267. # 解析RSS内存值
  268. rss_str = point['rss'].replace('MB', '')
  269. try:
  270. rss_values.append(float(rss_str))
  271. except ValueError:
  272. rss_values.append(0)
  273. # 解析VMS内存值
  274. vms_str = point['vms'].replace('MB', '')
  275. try:
  276. vms_values.append(float(vms_str))
  277. except ValueError:
  278. vms_values.append(0)
  279. # 解析CPU百分比
  280. cpu_str = point['cpu_pct'].replace('%', '')
  281. try:
  282. cpu_values.append(float(cpu_str))
  283. except ValueError:
  284. cpu_values.append(0)
  285. # 解析内存占用率
  286. mem_pct_str = point['mem_pct'].replace('%', '')
  287. try:
  288. mem_pct_values.append(float(mem_pct_str))
  289. except ValueError:
  290. mem_pct_values.append(0)
  291. # 检查是否有有效数据
  292. if not any([rss_values, vms_values, cpu_values, mem_pct_values]):
  293. ax.text(0.5, 0.5, '无有效进程资源数据', ha='center', va='center', transform=ax.transAxes,
  294. fontsize=12, color='#666666')
  295. ax.set_title('进程资源使用情况', fontsize=14, fontweight='bold', pad=20)
  296. return
  297. # 计算统计数据
  298. avg_cpu = np.mean(cpu_values) if cpu_values else 0
  299. max_cpu = max(cpu_values) if cpu_values else 0
  300. avg_rss = np.mean(rss_values) if rss_values else 0
  301. max_rss = max(rss_values) if rss_values else 0
  302. # 绘制双轴图
  303. ax2 = ax.twinx()
  304. lines = [] # 存储线条引用
  305. # 物理内存使用量(蓝色)
  306. if rss_values:
  307. line1, = ax.plot(relative_times, rss_values, marker='o', markersize=5, linewidth=2.5,
  308. color='#1976d2', label='物理内存(RSS)', markevery=slice(0, len(relative_times), max(1, len(relative_times)//15)))
  309. lines.append(line1)
  310. # 添加平均线
  311. ax.axhline(y=avg_rss, color='#1976d2', linestyle=':', linewidth=1.5, alpha=0.7,
  312. label=f'平均RSS: {avg_rss:.1f} MB')
  313. # 虚拟内存使用量(绿色)
  314. if vms_values:
  315. line3, = ax.plot(relative_times, vms_values, marker='^', markersize=6, linewidth=2.5,
  316. color='#388e3c', label='虚拟内存(VMS)', markevery=slice(0, len(relative_times), max(1, len(relative_times)//15)))
  317. lines.append(line3)
  318. ax.set_xlabel('时间 (秒)', fontsize=12, fontweight='bold')
  319. ax.set_ylabel('内存使用量 (MB)', fontsize=12, fontweight='bold', color='#1976d2')
  320. ax.tick_params(axis='y', labelcolor='#1976d2')
  321. line2 = None
  322. line4 = None
  323. # CPU使用率(红色)和内存占用率(紫色)
  324. if cpu_values:
  325. line2, = ax2.plot(relative_times, cpu_values, marker='s', markersize=5, linewidth=2.5,
  326. color='#d32f2f', label='CPU使用率', markevery=slice(0, len(relative_times), max(1, len(relative_times)//15)))
  327. lines.append(line2)
  328. # 添加CPU平均线和峰值标记
  329. ax2.axhline(y=avg_cpu, color='#d32f2f', linestyle=':', linewidth=1.5, alpha=0.7,
  330. label=f'平均CPU: {avg_cpu:.1f}%')
  331. # 寻找CPU峰值点并标记
  332. if cpu_values:
  333. max_cpu_idx = cpu_values.index(max_cpu)
  334. max_cpu_time = relative_times[max_cpu_idx]
  335. ax2.scatter(max_cpu_time, max_cpu, s=120, color='#d32f2f', marker='*',
  336. edgecolor='white', linewidth=1, zorder=5)
  337. if mem_pct_values:
  338. line4, = ax2.plot(relative_times, mem_pct_values, marker='d', markersize=6, linewidth=2.5,
  339. color='#7b1fa2', label='内存占用率', markevery=slice(0, len(relative_times), max(1, len(relative_times)//15)))
  340. lines.append(line4)
  341. ax2.set_ylabel('百分比 (%)', fontsize=12, fontweight='bold', color='#d32f2f')
  342. ax2.tick_params(axis='y', labelcolor='#d32f2f')
  343. # 合并图例
  344. if lines:
  345. labels = [l.get_label() for l in lines]
  346. ax.legend(lines, labels, loc='upper left', frameon=True, fancybox=True, shadow=True, ncol=2)
  347. # 添加统计信息文本框
  348. textstr = f'CPU峰值: {max_cpu:.1f}%\n内存峰值: {max_rss:.1f} MB'
  349. props = dict(boxstyle='round', facecolor='#ffebee', alpha=0.8)
  350. ax.text(0.03, 0.97, textstr, transform=ax.transAxes, fontsize=10,
  351. verticalalignment='top', bbox=props, fontweight='bold')
  352. ax.set_title('进程资源使用情况', fontsize=14, fontweight='bold', pad=20)
  353. ax.grid(True, alpha=0.4)
  354. def plot_network_data(ax, psutil_data):
  355. """绘制网络使用数据"""
  356. if not psutil_data or 'points' not in psutil_data:
  357. ax.text(0.5, 0.5, '无网络数据', ha='center', va='center', transform=ax.transAxes,
  358. fontsize=12, color='#666666')
  359. ax.set_title('系统网络使用情况', fontsize=14, fontweight='bold', pad=20)
  360. return
  361. points = psutil_data['points']
  362. if not points:
  363. ax.text(0.5, 0.5, '无网络数据', ha='center', va='center', transform=ax.transAxes,
  364. fontsize=12, color='#666666')
  365. ax.set_title('系统网络使用情况', fontsize=14, fontweight='bold', pad=20)
  366. return
  367. # 提取数据并转换时间字符串为时间戳
  368. from datetime import datetime
  369. timestamps = []
  370. net_recv_values = []
  371. net_send_values = []
  372. for point in points:
  373. # 解析时间字符串并转换为时间戳
  374. try:
  375. dt = datetime.strptime(point['now_time'], '%Y-%m-%d %H:%M:%S')
  376. timestamps.append(dt.timestamp())
  377. except ValueError:
  378. # 如果解析失败,使用索引作为后备
  379. timestamps.append(len(timestamps))
  380. # 转换为相对时间(秒)
  381. start_time = timestamps[0] if timestamps else 0
  382. relative_times = [t - start_time for t in timestamps]
  383. for point in points:
  384. # 解析网络接收值
  385. net_recv_str = point['sys_net_recv'].replace('MB/s', '').replace('MB', '').replace(' ', '')
  386. try:
  387. net_recv_values.append(float(net_recv_str))
  388. except ValueError:
  389. net_recv_values.append(0)
  390. # 解析网络发送值
  391. net_send_str = point['sys_net_send'].replace('MB/s', '').replace('MB', '').replace(' ', '')
  392. try:
  393. net_send_values.append(float(net_send_str))
  394. except ValueError:
  395. net_send_values.append(0)
  396. # 检查是否有有效数据
  397. if not net_recv_values and not net_send_values:
  398. ax.text(0.5, 0.5, '无有效网络数据', ha='center', va='center', transform=ax.transAxes,
  399. fontsize=12, color='#666666')
  400. ax.set_title('系统网络使用情况', fontsize=14, fontweight='bold', pad=20)
  401. return
  402. # 计算统计数据
  403. avg_recv = np.mean(net_recv_values) if net_recv_values else 0
  404. avg_send = np.mean(net_send_values) if net_send_values else 0
  405. max_recv = max(net_recv_values) if net_recv_values else 0
  406. max_send = max(net_send_values) if net_send_values else 0
  407. # 绘制网络使用情况,使用面积图增强视觉效果
  408. if net_recv_values:
  409. ax.plot(relative_times, net_recv_values, marker='o', markersize=5, linewidth=2.5,
  410. color='#1976d2', label='网络接收', markevery=slice(0, len(relative_times), max(1, len(relative_times)//15)))
  411. ax.fill_between(relative_times, net_recv_values, alpha=0.3, color='#1976d2')
  412. # 添加平均线
  413. ax.axhline(y=avg_recv, color='#1976d2', linestyle=':', linewidth=1.5, alpha=0.7,
  414. label=f'平均接收: {avg_recv:.2f} MB/s')
  415. if net_send_values:
  416. ax.plot(relative_times, net_send_values, marker='s', markersize=5, linewidth=2.5,
  417. color='#ff9800', label='网络发送', markevery=slice(0, len(relative_times), max(1, len(relative_times)//15)))
  418. ax.fill_between(relative_times, net_send_values, alpha=0.3, color='#ff9800')
  419. # 添加平均线
  420. ax.axhline(y=avg_send, color='#ff9800', linestyle=':', linewidth=1.5, alpha=0.7,
  421. label=f'平均发送: {avg_send:.2f} MB/s')
  422. # 添加统计信息文本框
  423. textstr = f'接收峰值: {max_recv:.2f} MB/s\n发送峰值: {max_send:.2f} MB/s'
  424. props = dict(boxstyle='round', facecolor='#e3f2fd', alpha=0.8)
  425. ax.text(0.03, 0.97, textstr, transform=ax.transAxes, fontsize=10,
  426. verticalalignment='top', bbox=props, fontweight='bold')
  427. ax.set_xlabel('时间 (秒)', fontsize=12, fontweight='bold')
  428. ax.set_ylabel('速率 (MB/s)', fontsize=12, fontweight='bold')
  429. ax.set_title('系统网络使用情况', fontsize=14, fontweight='bold', pad=20)
  430. ax.grid(True, alpha=0.4)
  431. ax.legend(loc='upper right', frameon=True, fancybox=True, shadow=True, ncol=1)
  432. def plot_disk_io_data(ax, psutil_data):
  433. """绘制进程IO数据"""
  434. if not psutil_data or 'points' not in psutil_data:
  435. ax.text(0.5, 0.5, '无进程IO数据', ha='center', va='center', transform=ax.transAxes,
  436. fontsize=12, color='#666666')
  437. ax.set_title('进程IO情况', fontsize=14, fontweight='bold', pad=20)
  438. return
  439. points = psutil_data['points']
  440. if not points:
  441. ax.text(0.5, 0.5, '无进程IO数据', ha='center', va='center', transform=ax.transAxes,
  442. fontsize=12, color='#666666')
  443. ax.set_title('进程IO情况', fontsize=14, fontweight='bold', pad=20)
  444. return
  445. # 提取数据并转换时间字符串为时间戳
  446. timestamps = []
  447. disk_read_values = []
  448. disk_write_values = []
  449. for point in points:
  450. # 解析时间字符串并转换为时间戳
  451. try:
  452. dt = datetime.strptime(point['now_time'], '%Y-%m-%d %H:%M:%S')
  453. timestamps.append(dt.timestamp())
  454. except ValueError:
  455. # 如果解析失败,使用索引作为后备
  456. timestamps.append(len(timestamps))
  457. # 转换为相对时间(秒)
  458. start_time = timestamps[0] if timestamps else 0
  459. relative_times = [t - start_time for t in timestamps]
  460. for point in points:
  461. # 解析磁盘读取值
  462. disk_read_str = point['disk_read'].replace('MB/s', '').replace('MB', '').replace(' ', '')
  463. try:
  464. disk_read_values.append(float(disk_read_str))
  465. except ValueError:
  466. disk_read_values.append(0)
  467. # 解析磁盘写入值
  468. disk_write_str = point['disk_write'].replace('MB/s', '').replace('MB', '').replace(' ', '')
  469. try:
  470. disk_write_values.append(float(disk_write_str))
  471. except ValueError:
  472. disk_write_values.append(0)
  473. # 检查是否有有效数据
  474. if not disk_read_values and not disk_write_values:
  475. ax.text(0.5, 0.5, '无有效IO数据', ha='center', va='center', transform=ax.transAxes,
  476. fontsize=12, color='#666666')
  477. ax.set_title('进程IO情况', fontsize=14, fontweight='bold', pad=20)
  478. return
  479. # 计算统计数据
  480. avg_read = np.mean(disk_read_values) if disk_read_values else 0
  481. avg_write = np.mean(disk_write_values) if disk_write_values else 0
  482. max_read = max(disk_read_values) if disk_read_values else 0
  483. max_write = max(disk_write_values) if disk_write_values else 0
  484. # 绘制磁盘IO情况,使用不同样式的线条区分读写操作
  485. if disk_read_values:
  486. ax.plot(relative_times, disk_read_values, marker='o', markersize=5, linewidth=2.5,
  487. color='#7b1fa2', label='磁盘读取', linestyle='-', markevery=slice(0, len(relative_times), max(1, len(relative_times)//15)))
  488. ax.fill_between(relative_times, disk_read_values, alpha=0.3, color='#7b1fa2')
  489. # 添加平均线
  490. ax.axhline(y=avg_read, color='#7b1fa2', linestyle=':', linewidth=1.5, alpha=0.7,
  491. label=f'平均读取: {avg_read:.2f} MB/s')
  492. if disk_write_values:
  493. ax.plot(relative_times, disk_write_values, marker='s', markersize=5, linewidth=2.5,
  494. color='#f57c00', label='磁盘写入', linestyle='--', markevery=slice(0, len(relative_times), max(1, len(relative_times)//15)))
  495. ax.fill_between(relative_times, disk_write_values, alpha=0.3, color='#f57c00')
  496. # 添加平均线
  497. ax.axhline(y=avg_write, color='#f57c00', linestyle=':', linewidth=1.5, alpha=0.7,
  498. label=f'平均写入: {avg_write:.2f} MB/s')
  499. # 添加统计信息文本框
  500. textstr = f'读取峰值: {max_read:.2f} MB/s\n写入峰值: {max_write:.2f} MB/s'
  501. props = dict(boxstyle='round', facecolor='#f3e5f5', alpha=0.8)
  502. ax.text(0.03, 0.97, textstr, transform=ax.transAxes, fontsize=10,
  503. verticalalignment='top', bbox=props, fontweight='bold')
  504. ax.set_xlabel('时间 (秒)', fontsize=12, fontweight='bold')
  505. ax.set_ylabel('速率 (MB/s)', fontsize=12, fontweight='bold')
  506. ax.set_title('进程IO情况', fontsize=14, fontweight='bold', pad=20)
  507. ax.grid(True, alpha=0.4)
  508. ax.legend(loc='upper right', frameon=True, fancybox=True, shadow=True, ncol=1)
  509. def main():
  510. parser = argparse.ArgumentParser(description='绘制性能分析结果图')
  511. # parser.add_argument('--input','-i', required=True)
  512. # parser.add_argument('--output', '-o',required=True)
  513. parser.add_argument('--input','-i', default='./results/performance_analysis_report.json')
  514. parser.add_argument('--output', '-o', default='./results/performance_analysis_report.png')
  515. args = parser.parse_args()
  516. if not os.path.exists(args.input):
  517. raise ValueError(f'输入文件不存在:{args.input}')
  518. # 读取json数据
  519. data = read_json(args.input)
  520. # 创建更大的图表
  521. fig = plt.figure(figsize=(20, 18))
  522. fig.suptitle('Python 性能分析报告', fontsize=24, fontweight='bold', y=0.95)
  523. # 设置图表背景色
  524. fig.patch.set_facecolor('white')
  525. # 添加水印效果
  526. fig.text(0.5, 0.5, 'Python Performance Report', fontsize=40, color='gray',
  527. ha='center', va='center', alpha=0.05, rotation=45)
  528. # 创建子图
  529. # cProfile 表格
  530. ax1 = plt.subplot2grid((4, 2), (0, 0), colspan=1)
  531. # cProfile 柱状图
  532. ax2 = plt.subplot2grid((4, 2), (0, 1), colspan=1)
  533. # 内存使用情况
  534. ax3 = plt.subplot2grid((4, 2), (1, 0), colspan=1)
  535. # 系统资源监控
  536. ax4 = plt.subplot2grid((4, 2), (1, 1), colspan=1)
  537. # 网络使用情况
  538. ax5 = plt.subplot2grid((4, 2), (2, 0), colspan=1)
  539. # 磁盘读写情况
  540. ax6 = plt.subplot2grid((4, 2), (2, 1), colspan=1)
  541. # 绘制各个子图
  542. plot_cprofile_table(ax1, data.get('analysis_results', {}).get('cprofile', {}))
  543. plot_cprofile_bar_chart(ax2, data.get('analysis_results', {}).get('cprofile', {}))
  544. plot_memory_data(ax3, data.get('analysis_results', {}).get('memory_profile', {}))
  545. plot_psutil_data(ax4, data.get('analysis_results', {}).get('psutil', {}))
  546. plot_network_data(ax5, data.get('analysis_results', {}).get('psutil', {}))
  547. plot_disk_io_data(ax6, data.get('analysis_results', {}).get('psutil', {}))
  548. # 调整布局
  549. plt.tight_layout(rect=[0, 0.01, 1, 0.93]) # 减少底部边距以减少空白区域
  550. # 添加页脚信息
  551. fig.text(0.95, 0.005, f'生成时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}',
  552. fontsize=10, ha='right', va='bottom', color='#666666')
  553. fig.text(0.05, 0.005, 'Python性能分析工具', fontsize=10, ha='left', va='bottom', color='#666666')
  554. # 保存图像
  555. plt.savefig(args.output, dpi=300, bbox_inches='tight', facecolor='white',
  556. edgecolor='none', orientation='landscape')
  557. print(f"性能分析图表已保存至: {args.output}")
  558. plt.close() # 关闭图形以释放内存
  559. # plt.show() # 可选:显示图表
  560. if __name__ == '__main__':
  561. main()