plot.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  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. textstr = f'CPU峰值: {max_cpu:.1f}%\n内存峰值: {max_rss:.1f} MB'
  345. props = dict(boxstyle='round', facecolor='#ffebee', alpha=0.8)
  346. # 将统计信息放在左上角
  347. ax.text(0.03, 0.97, textstr, transform=ax.transAxes, fontsize=10,
  348. verticalalignment='top', bbox=props, fontweight='bold')
  349. # 合并图例,放置在右侧避免与统计信息重叠
  350. if lines:
  351. labels = [l.get_label() for l in lines]
  352. # 将图例放在左上角,但在统计信息文本框的下方
  353. ax.legend(lines, labels, loc='upper left', frameon=True, fancybox=True,
  354. shadow=True, ncol=1, bbox_to_anchor=(0.02, 0.85)) # 调整bbox_to_anchor来控制具体位置
  355. ax.set_title('进程资源使用情况', fontsize=14, fontweight='bold', pad=20)
  356. ax.grid(True, alpha=0.4)
  357. def plot_network_data(ax, psutil_data):
  358. """绘制网络使用数据"""
  359. if not psutil_data or 'points' not in psutil_data:
  360. ax.text(0.5, 0.5, '无网络数据', ha='center', va='center', transform=ax.transAxes,
  361. fontsize=12, color='#666666')
  362. ax.set_title('系统网络使用情况', fontsize=14, fontweight='bold', pad=20)
  363. return
  364. points = psutil_data['points']
  365. if not points:
  366. ax.text(0.5, 0.5, '无网络数据', ha='center', va='center', transform=ax.transAxes,
  367. fontsize=12, color='#666666')
  368. ax.set_title('系统网络使用情况', fontsize=14, fontweight='bold', pad=20)
  369. return
  370. # 提取数据并转换时间字符串为时间戳
  371. from datetime import datetime
  372. timestamps = []
  373. net_recv_values = []
  374. net_send_values = []
  375. for point in points:
  376. # 解析时间字符串并转换为时间戳
  377. try:
  378. dt = datetime.strptime(point['now_time'], '%Y-%m-%d %H:%M:%S')
  379. timestamps.append(dt.timestamp())
  380. except ValueError:
  381. # 如果解析失败,使用索引作为后备
  382. timestamps.append(len(timestamps))
  383. # 转换为相对时间(秒)
  384. start_time = timestamps[0] if timestamps else 0
  385. relative_times = [t - start_time for t in timestamps]
  386. for point in points:
  387. # 解析网络接收值
  388. net_recv_str = point['sys_net_recv'].replace('MB/s', '').replace('MB', '').replace(' ', '')
  389. try:
  390. net_recv_values.append(float(net_recv_str))
  391. except ValueError:
  392. net_recv_values.append(0)
  393. # 解析网络发送值
  394. net_send_str = point['sys_net_send'].replace('MB/s', '').replace('MB', '').replace(' ', '')
  395. try:
  396. net_send_values.append(float(net_send_str))
  397. except ValueError:
  398. net_send_values.append(0)
  399. # 检查是否有有效数据
  400. if not net_recv_values and not net_send_values:
  401. ax.text(0.5, 0.5, '无有效网络数据', ha='center', va='center', transform=ax.transAxes,
  402. fontsize=12, color='#666666')
  403. ax.set_title('系统网络使用情况', fontsize=14, fontweight='bold', pad=20)
  404. return
  405. # 计算统计数据
  406. avg_recv = np.mean(net_recv_values) if net_recv_values else 0
  407. avg_send = np.mean(net_send_values) if net_send_values else 0
  408. max_recv = max(net_recv_values) if net_recv_values else 0
  409. max_send = max(net_send_values) if net_send_values else 0
  410. # 绘制网络使用情况,使用面积图增强视觉效果
  411. if net_recv_values:
  412. ax.plot(relative_times, net_recv_values, marker='o', markersize=5, linewidth=2.5,
  413. color='#1976d2', label='网络接收', markevery=slice(0, len(relative_times), max(1, len(relative_times)//15)))
  414. ax.fill_between(relative_times, net_recv_values, alpha=0.3, color='#1976d2')
  415. # 添加平均线
  416. ax.axhline(y=avg_recv, color='#1976d2', linestyle=':', linewidth=1.5, alpha=0.7,
  417. label=f'平均接收: {avg_recv:.2f} MB/s')
  418. if net_send_values:
  419. ax.plot(relative_times, net_send_values, marker='s', markersize=5, linewidth=2.5,
  420. color='#ff9800', label='网络发送', markevery=slice(0, len(relative_times), max(1, len(relative_times)//15)))
  421. ax.fill_between(relative_times, net_send_values, alpha=0.3, color='#ff9800')
  422. # 添加平均线
  423. ax.axhline(y=avg_send, color='#ff9800', linestyle=':', linewidth=1.5, alpha=0.7,
  424. label=f'平均发送: {avg_send:.2f} MB/s')
  425. # 添加统计信息文本框
  426. textstr = f'接收峰值: {max_recv:.2f} MB/s\n发送峰值: {max_send:.2f} MB/s'
  427. props = dict(boxstyle='round', facecolor='#e3f2fd', alpha=0.8)
  428. ax.text(0.03, 0.97, textstr, transform=ax.transAxes, fontsize=10,
  429. verticalalignment='top', bbox=props, fontweight='bold')
  430. ax.set_xlabel('时间 (秒)', fontsize=12, fontweight='bold')
  431. ax.set_ylabel('速率 (MB/s)', fontsize=12, fontweight='bold')
  432. ax.set_title('系统网络使用情况', fontsize=14, fontweight='bold', pad=20)
  433. ax.grid(True, alpha=0.4)
  434. ax.legend(loc='upper right', frameon=True, fancybox=True, shadow=True, ncol=1)
  435. def plot_disk_io_data(ax, psutil_data):
  436. """绘制进程IO数据"""
  437. if not psutil_data or 'points' not in psutil_data:
  438. ax.text(0.5, 0.5, '无进程IO数据', ha='center', va='center', transform=ax.transAxes,
  439. fontsize=12, color='#666666')
  440. ax.set_title('进程IO情况', fontsize=14, fontweight='bold', pad=20)
  441. return
  442. points = psutil_data['points']
  443. if not points:
  444. ax.text(0.5, 0.5, '无进程IO数据', ha='center', va='center', transform=ax.transAxes,
  445. fontsize=12, color='#666666')
  446. ax.set_title('进程IO情况', fontsize=14, fontweight='bold', pad=20)
  447. return
  448. # 提取数据并转换时间字符串为时间戳
  449. timestamps = []
  450. disk_read_values = []
  451. disk_write_values = []
  452. for point in points:
  453. # 解析时间字符串并转换为时间戳
  454. try:
  455. dt = datetime.strptime(point['now_time'], '%Y-%m-%d %H:%M:%S')
  456. timestamps.append(dt.timestamp())
  457. except ValueError:
  458. # 如果解析失败,使用索引作为后备
  459. timestamps.append(len(timestamps))
  460. # 转换为相对时间(秒)
  461. start_time = timestamps[0] if timestamps else 0
  462. relative_times = [t - start_time for t in timestamps]
  463. for point in points:
  464. # 解析磁盘读取值
  465. disk_read_str = point['disk_read'].replace('MB/s', '').replace('MB', '').replace(' ', '')
  466. try:
  467. disk_read_values.append(float(disk_read_str))
  468. except ValueError:
  469. disk_read_values.append(0)
  470. # 解析磁盘写入值
  471. disk_write_str = point['disk_write'].replace('MB/s', '').replace('MB', '').replace(' ', '')
  472. try:
  473. disk_write_values.append(float(disk_write_str))
  474. except ValueError:
  475. disk_write_values.append(0)
  476. # 检查是否有有效数据
  477. if not disk_read_values and not disk_write_values:
  478. ax.text(0.5, 0.5, '无有效IO数据', ha='center', va='center', transform=ax.transAxes,
  479. fontsize=12, color='#666666')
  480. ax.set_title('进程IO情况', fontsize=14, fontweight='bold', pad=20)
  481. return
  482. # 计算统计数据
  483. avg_read = np.mean(disk_read_values) if disk_read_values else 0
  484. avg_write = np.mean(disk_write_values) if disk_write_values else 0
  485. max_read = max(disk_read_values) if disk_read_values else 0
  486. max_write = max(disk_write_values) if disk_write_values else 0
  487. # 绘制磁盘IO情况,使用不同样式的线条区分读写操作
  488. if disk_read_values:
  489. ax.plot(relative_times, disk_read_values, marker='o', markersize=5, linewidth=2.5,
  490. color='#7b1fa2', label='磁盘读取', linestyle='-', markevery=slice(0, len(relative_times), max(1, len(relative_times)//15)))
  491. ax.fill_between(relative_times, disk_read_values, alpha=0.3, color='#7b1fa2')
  492. # 添加平均线
  493. ax.axhline(y=avg_read, color='#7b1fa2', linestyle=':', linewidth=1.5, alpha=0.7,
  494. label=f'平均读取: {avg_read:.2f} MB/s')
  495. if disk_write_values:
  496. ax.plot(relative_times, disk_write_values, marker='s', markersize=5, linewidth=2.5,
  497. color='#f57c00', label='磁盘写入', linestyle='--', markevery=slice(0, len(relative_times), max(1, len(relative_times)//15)))
  498. ax.fill_between(relative_times, disk_write_values, alpha=0.3, color='#f57c00')
  499. # 添加平均线
  500. ax.axhline(y=avg_write, color='#f57c00', linestyle=':', linewidth=1.5, alpha=0.7,
  501. label=f'平均写入: {avg_write:.2f} MB/s')
  502. # 添加统计信息文本框
  503. textstr = f'读取峰值: {max_read:.2f} MB/s\n写入峰值: {max_write:.2f} MB/s'
  504. props = dict(boxstyle='round', facecolor='#f3e5f5', alpha=0.8)
  505. ax.text(0.03, 0.97, textstr, transform=ax.transAxes, fontsize=10,
  506. verticalalignment='top', bbox=props, fontweight='bold')
  507. ax.set_xlabel('时间 (秒)', fontsize=12, fontweight='bold')
  508. ax.set_ylabel('速率 (MB/s)', fontsize=12, fontweight='bold')
  509. ax.set_title('进程IO情况', fontsize=14, fontweight='bold', pad=20)
  510. ax.grid(True, alpha=0.4)
  511. ax.legend(loc='upper right', frameon=True, fancybox=True, shadow=True, ncol=1)
  512. def main():
  513. parser = argparse.ArgumentParser(description='绘制性能分析结果图')
  514. # parser.add_argument('--input','-i', required=True)
  515. # parser.add_argument('--output', '-o',required=True)
  516. parser.add_argument('--input','-i', default='./results/performance_analysis_report.json')
  517. parser.add_argument('--output', '-o', default='./results/performance_analysis_report.png')
  518. args = parser.parse_args()
  519. if not os.path.exists(args.input):
  520. raise ValueError(f'输入文件不存在:{args.input}')
  521. # 读取json数据
  522. data = read_json(args.input)
  523. # 创建更大的图表
  524. fig = plt.figure(figsize=(24, 16))
  525. fig.suptitle('Python 性能分析报告', fontsize=24, fontweight='bold', y=0.95)
  526. # 设置图表背景色
  527. fig.patch.set_facecolor('white')
  528. # 添加水印效果
  529. fig.text(0.5, 0.5, 'Python Performance Report', fontsize=40, color='gray',
  530. ha='center', va='center', alpha=0.05, rotation=45)
  531. # 创建子图,增加每行的高度
  532. # cProfile 表格
  533. ax1 = plt.subplot2grid((10, 2), (0, 0), rowspan=3, colspan=1)
  534. # cProfile 柱状图
  535. ax2 = plt.subplot2grid((10, 2), (0, 1), rowspan=3,colspan=1)
  536. # 内存使用情况
  537. ax3 = plt.subplot2grid((10, 2), (3, 0), rowspan=4,colspan=1)
  538. # 系统资源监控
  539. ax4 = plt.subplot2grid((10, 2), (3, 1), rowspan=4,colspan=1)
  540. # 网络使用情况
  541. ax5 = plt.subplot2grid((10, 2), (7, 0), rowspan=3, colspan=1)
  542. # 磁盘读写情况
  543. ax6 = plt.subplot2grid((10, 2), (7, 1), rowspan=3, colspan=1)
  544. # 绘制各个子图
  545. plot_cprofile_table(ax1, data.get('analysis_results', {}).get('cprofile', {}))
  546. plot_cprofile_bar_chart(ax2, data.get('analysis_results', {}).get('cprofile', {}))
  547. plot_memory_data(ax3, data.get('analysis_results', {}).get('memory_profile', {}))
  548. plot_psutil_data(ax4, data.get('analysis_results', {}).get('psutil', {}))
  549. plot_network_data(ax5, data.get('analysis_results', {}).get('psutil', {}))
  550. plot_disk_io_data(ax6, data.get('analysis_results', {}).get('psutil', {}))
  551. # 调整布局
  552. plt.tight_layout(rect=(0, 0.01, 1, 0.93)) # 减少底部边距以减少空白区域
  553. # 添加页脚信息
  554. fig.text(0.95, 0.005, f'生成时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}',
  555. fontsize=10, ha='right', va='bottom', color='#666666')
  556. fig.text(0.05, 0.005, 'Python性能分析工具', fontsize=10, ha='left', va='bottom', color='#666666')
  557. # 保存图像
  558. plt.savefig(args.output, dpi=300, bbox_inches='tight', facecolor='white',
  559. edgecolor='none', orientation='landscape')
  560. print(f"性能分析图表已保存至: {args.output}")
  561. plt.close() # 关闭图形以释放内存
  562. # plt.show() # 可选:显示图表
  563. if __name__ == '__main__':
  564. main()