plot.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. import json
  2. import argparse
  3. import matplotlib.pyplot as plt
  4. import numpy as np
  5. from datetime import datetime
  6. from matplotlib.font_manager import FontProperties
  7. # 设置中文字体支持
  8. plt.rcParams['font.sans-serif'] = ['SimHei', 'FangSong', 'Arial Unicode MS']
  9. plt.rcParams['axes.unicode_minus'] = False
  10. def read_json(file_path):
  11. with open(file_path, 'r', encoding='utf-8') as f:
  12. data = json.load(f)
  13. return data
  14. def plot_cprofile_table(ax, cprofile_data):
  15. """绘制cProfile详细信息表格"""
  16. if not cprofile_data or 'points' not in cprofile_data:
  17. ax.text(0.5, 0.5, '无cProfile数据', ha='center', va='center', transform=ax.transAxes)
  18. ax.set_title('函数运行详细信息')
  19. return
  20. # 只取前15个最耗时的函数
  21. fun_num = 15
  22. points = cprofile_data['points'][:fun_num]
  23. # 准备表格数据
  24. table_data = []
  25. for point in points:
  26. # 简化函数名显示
  27. func_name = point['function'][1:-1]
  28. func_name = func_name.split(',')[-1].strip()
  29. func_name = func_name[1:-1]
  30. # 限制函数名长度,但保留足够空间显示完整信息
  31. func_name_display = func_name[:35] + '...' if len(func_name) > 35 else func_name
  32. # 将数据点添加到表格
  33. table_data.append([
  34. func_name_display,
  35. point['call_count'],
  36. f"{point['total_time']:.6f}",
  37. f"{point['cumulative_time']:.6f}"
  38. ])
  39. # 创建表格
  40. columns = ['函数名称', '调用次数', '总时间(s)', '累计时间(s)']
  41. table = ax.table(cellText=table_data, colLabels=columns, cellLoc='center', loc='center')
  42. table.auto_set_font_size(False)
  43. table.set_fontsize(9)
  44. table.scale(1, 1.5)
  45. # 设置表格样式
  46. for i in range(len(columns)):
  47. table[(0, i)].set_facecolor('#4CAF50')
  48. table[(0, i)].set_text_props(weight='bold', color='white')
  49. # 根据函数数量动态调整标题距离
  50. # 每个函数行大约需要8个单位的高度,加上基础padding
  51. pad_value = 8 + len(points) * 3
  52. ax.set_title(f'函数运行详细信息 (Top {fun_num} 函数)', fontsize=14, pad=pad_value)
  53. # 坐标轴掩藏
  54. ax.set_xticks([])
  55. ax.set_yticks([])
  56. ax.spines['top'].set_visible(False)
  57. ax.spines['right'].set_visible(False)
  58. ax.spines['bottom'].set_visible(False)
  59. ax.spines['left'].set_visible(False)
  60. def plot_cprofile_bar_chart(ax, cprofile_data):
  61. """绘制cProfile总时间柱状图"""
  62. if not cprofile_data or 'points' not in cprofile_data:
  63. ax.text(0.5, 0.5, '无cProfile数据', ha='center', va='center', transform=ax.transAxes)
  64. ax.set_title('函数运行总时间')
  65. return
  66. # 只取前N个最耗时的函数
  67. fun_num = 15
  68. points = cprofile_data['points'][:fun_num]
  69. # 提取函数名和总时间
  70. functions = []
  71. total_times = []
  72. for point in points:
  73. # 简化函数名显示
  74. func_name = point['function'][1:-1]
  75. func_name = func_name.split(',')[-1].strip()
  76. func_name = func_name[1:-1]
  77. # 限制函数名长度
  78. func_name_display = func_name[:35] + '...' if len(func_name) > 35 else func_name
  79. functions.append(func_name_display)
  80. total_times.append(point['total_time'])
  81. # 创建垂直柱状图,使用渐变色彩
  82. x_pos = np.arange(len(functions))
  83. colors = plt.cm.viridis(np.linspace(0, 1, len(functions)))
  84. bars = ax.bar(x_pos, total_times, color=colors)
  85. # 设置坐标轴
  86. ax.set_xticks(x_pos)
  87. ax.set_xticklabels(functions, rotation=30, ha='right', fontsize=9)
  88. ax.set_ylabel('总时间 (秒)', fontsize=12)
  89. ax.set_title(f'函数运行总时间 (Top {fun_num} 函数)', fontsize=14, pad=20)
  90. # 改善网格线样式
  91. ax.grid(axis='y', alpha=0.7, linestyle='--', linewidth=0.5)
  92. ax.set_axisbelow(True)
  93. # 在柱状图上添加数值标签,优化显示效果
  94. for bar, time in zip(bars, total_times):
  95. height = bar.get_height()
  96. ax.text(bar.get_x() + bar.get_width()/2., height,
  97. f'{time:.6f}', ha='center', va='bottom', fontsize=8,
  98. rotation=0, weight='bold')
  99. # 添加边框
  100. for spine in ax.spines.values():
  101. spine.set_linewidth(0.5)
  102. def plot_memory_data(ax, memory_data):
  103. """绘制内存使用数据"""
  104. if not memory_data or 'points' not in memory_data:
  105. ax.text(0.5, 0.5, '无内存数据', ha='center', va='center', transform=ax.transAxes)
  106. ax.set_title('memory_profiler 内存使用情况')
  107. return
  108. timestamps = [point['timestamp'] for point in memory_data['points']]
  109. memory_values = [point['memory'] for point in memory_data['points']]
  110. # 转换时间戳为相对时间(秒)
  111. start_time = timestamps[0] if timestamps else 0
  112. relative_times = [t - start_time for t in timestamps]
  113. # 查找内存峰值及其位置
  114. max_memory = max(memory_values) if memory_values else 0
  115. max_index = memory_values.index(max_memory) if memory_values else 0
  116. max_time = relative_times[max_index] if relative_times else 0
  117. # 绘制内存使用情况曲线
  118. line, = ax.plot(relative_times, memory_values, marker='o', markersize=3, linewidth=1.5)
  119. # 在峰值点添加特殊标记
  120. ax.plot(max_time, max_memory, marker='o', markersize=8, color='red',
  121. markerfacecolor='none', markeredgewidth=2, label=f'峰值: {max_memory:.2f} MB')
  122. ax.set_xlabel('时间 (秒)')
  123. ax.set_ylabel('内存使用量 (MB)')
  124. ax.set_title(f'进程内存使用情况 (峰值: {max_memory:.2f} MB at {max_time:.1f}s)')
  125. ax.grid(True, alpha=0.3)
  126. ax.legend()
  127. def plot_psutil_data(ax, psutil_data):
  128. """绘制psutil系统资源数据"""
  129. if not psutil_data or 'points' not in psutil_data:
  130. ax.text(0.5, 0.5, '无进程资源数据', ha='center', va='center', transform=ax.transAxes)
  131. ax.set_title('进程资源使用情况')
  132. return
  133. points = psutil_data['points']
  134. if not points:
  135. ax.text(0.5, 0.5, '无进程资源数据', ha='center', va='center', transform=ax.transAxes)
  136. ax.set_title('进程资源使用情况')
  137. return
  138. # 提取数据并转换时间字符串为时间戳
  139. timestamps = []
  140. rss_values = []
  141. vms_values = []
  142. cpu_values = []
  143. mem_pct_values = []
  144. for point in points:
  145. # 解析时间字符串并转换为时间戳
  146. try:
  147. dt = datetime.strptime(point['now_time'], '%Y-%m-%d %H:%M:%S')
  148. timestamps.append(dt.timestamp())
  149. except ValueError:
  150. # 如果解析失败,使用索引作为后备
  151. timestamps.append(len(timestamps))
  152. # 转换时间戳为相对时间(秒)
  153. start_time = timestamps[0] if timestamps else 0
  154. relative_times = [t - start_time for t in timestamps]
  155. for point in points:
  156. # 解析RSS内存值
  157. rss_str = point['rss'].replace('MB', '')
  158. try:
  159. rss_values.append(float(rss_str))
  160. except ValueError:
  161. rss_values.append(0)
  162. # 解析VMS内存值
  163. vms_str = point['vms'].replace('MB', '')
  164. try:
  165. vms_values.append(float(vms_str))
  166. except ValueError:
  167. vms_values.append(0)
  168. # 解析CPU百分比
  169. cpu_str = point['cpu_pct'].replace('%', '')
  170. try:
  171. cpu_values.append(float(cpu_str))
  172. except ValueError:
  173. cpu_values.append(0)
  174. # 解析内存占用率
  175. mem_pct_str = point['mem_pct'].replace('%', '')
  176. try:
  177. mem_pct_values.append(float(mem_pct_str))
  178. except ValueError:
  179. mem_pct_values.append(0)
  180. # 绘制双轴图
  181. ax2 = ax.twinx()
  182. # 物理内存使用量(蓝色)
  183. line1, = ax.plot(relative_times, rss_values, marker='o', color='blue', label='物理内存(RSS)')
  184. # 虚拟内存使用量(绿色)
  185. line3, = ax.plot(relative_times, vms_values, marker='^', color='green', label='虚拟内存(VMS)')
  186. ax.set_xlabel('时间 (秒)')
  187. ax.set_ylabel('内存使用量 (MB)', color='blue')
  188. ax.tick_params(axis='y', labelcolor='blue')
  189. # CPU使用率(红色)和内存占用率(紫色)
  190. line2, = ax2.plot(relative_times, cpu_values, marker='s', color='red', label='CPU使用率')
  191. line4, = ax2.plot(relative_times, mem_pct_values, marker='d', color='purple', label='内存占用率')
  192. ax2.set_ylabel('百分比 (%)', color='red')
  193. ax2.tick_params(axis='y', labelcolor='red')
  194. # 合并图例
  195. lines = [line1, line3, line2, line4]
  196. labels = [l.get_label() for l in lines]
  197. ax.legend(lines, labels, loc='upper left')
  198. ax.set_title('进程资源使用情况')
  199. ax.grid(True, alpha=0.3)
  200. def plot_network_data(ax, psutil_data):
  201. """绘制网络使用数据"""
  202. if not psutil_data or 'points' not in psutil_data:
  203. ax.text(0.5, 0.5, '无网络数据', ha='center', va='center', transform=ax.transAxes)
  204. ax.set_title('系统网络使用情况')
  205. return
  206. points = psutil_data['points']
  207. if not points:
  208. ax.text(0.5, 0.5, '无网络数据', ha='center', va='center', transform=ax.transAxes)
  209. ax.set_title('系统网络使用情况')
  210. return
  211. # 提取数据并转换时间字符串为时间戳
  212. from datetime import datetime
  213. timestamps = []
  214. net_recv_values = []
  215. net_send_values = []
  216. for point in points:
  217. # 解析时间字符串并转换为时间戳
  218. try:
  219. dt = datetime.strptime(point['now_time'], '%Y-%m-%d %H:%M:%S')
  220. timestamps.append(dt.timestamp())
  221. except ValueError:
  222. # 如果解析失败,使用索引作为后备
  223. timestamps.append(len(timestamps))
  224. # 转换为相对时间(秒)
  225. start_time = timestamps[0] if timestamps else 0
  226. relative_times = [t - start_time for t in timestamps]
  227. for point in points:
  228. # 解析网络接收值
  229. net_recv_str = point['sys_net_recv'].replace('MB/s', '').replace('MB', '').replace(' ', '')
  230. try:
  231. net_recv_values.append(float(net_recv_str))
  232. except ValueError:
  233. net_recv_values.append(0)
  234. # 解析网络发送值
  235. net_send_str = point['sys_net_send'].replace('MB/s', '').replace('MB', '').replace(' ', '')
  236. try:
  237. net_send_values.append(float(net_send_str))
  238. except ValueError:
  239. net_send_values.append(0)
  240. # 绘制网络使用情况
  241. ax.plot(relative_times, net_recv_values, marker='o', color='blue', label='网络接收')
  242. ax.plot(relative_times, net_send_values, marker='s', color='red', label='网络发送')
  243. ax.set_xlabel('时间 (秒)')
  244. ax.set_ylabel('速率 (MB/s)')
  245. ax.set_title('系统网络使用情况')
  246. ax.grid(True, alpha=0.3)
  247. ax.legend()
  248. def plot_disk_io_data(ax, psutil_data):
  249. """绘制进程IO数据"""
  250. if not psutil_data or 'points' not in psutil_data:
  251. ax.text(0.5, 0.5, '无进程IO数据', ha='center', va='center', transform=ax.transAxes)
  252. ax.set_title('进程IO情况')
  253. return
  254. points = psutil_data['points']
  255. if not points:
  256. ax.text(0.5, 0.5, '无进程IO数据', ha='center', va='center', transform=ax.transAxes)
  257. ax.set_title('进程IO情况')
  258. return
  259. # 提取数据并转换时间字符串为时间戳
  260. timestamps = []
  261. disk_read_values = []
  262. disk_write_values = []
  263. for point in points:
  264. # 解析时间字符串并转换为时间戳
  265. try:
  266. dt = datetime.strptime(point['now_time'], '%Y-%m-%d %H:%M:%S')
  267. timestamps.append(dt.timestamp())
  268. except ValueError:
  269. # 如果解析失败,使用索引作为后备
  270. timestamps.append(len(timestamps))
  271. # 转换为相对时间(秒)
  272. start_time = timestamps[0] if timestamps else 0
  273. relative_times = [t - start_time for t in timestamps]
  274. for point in points:
  275. # 解析磁盘读取值
  276. disk_read_str = point['disk_read'].replace('MB/s', '').replace('MB', '').replace(' ', '')
  277. try:
  278. disk_read_values.append(float(disk_read_str))
  279. except ValueError:
  280. disk_read_values.append(0)
  281. # 解析磁盘写入值
  282. disk_write_str = point['disk_write'].replace('MB/s', '').replace('MB', '').replace(' ', '')
  283. try:
  284. disk_write_values.append(float(disk_write_str))
  285. except ValueError:
  286. disk_write_values.append(0)
  287. # 绘制磁盘IO情况
  288. ax.plot(relative_times, disk_read_values, marker='o', color='blue', label='磁盘读取')
  289. ax.plot(relative_times, disk_write_values, marker='s', color='red', label='磁盘写入')
  290. ax.set_xlabel('时间 (秒)')
  291. ax.set_ylabel('速率 (MB/s)')
  292. ax.set_title('进程IO情况')
  293. ax.grid(True, alpha=0.3)
  294. ax.legend()
  295. def main():
  296. parser = argparse.ArgumentParser(description='绘制性能分析结果图')
  297. parser.add_argument('--input','-i', default='./results/performance_analysis_report.json')
  298. parser.add_argument('--output', '-o', default='./results/performance_analysis_report.png')
  299. args = parser.parse_args()
  300. # 读取json数据
  301. data = read_json(args.input)
  302. # 创建更大的图表
  303. fig = plt.figure(figsize=(20, 18))
  304. fig.suptitle('Python 性能分析报告', fontsize=20, fontweight='bold')
  305. # 创建子图
  306. # cProfile 表格
  307. ax1 = plt.subplot2grid((4, 2), (0, 0), colspan=1)
  308. # cProfile 柱状图
  309. ax2 = plt.subplot2grid((4, 2), (0, 1), colspan=1)
  310. # 内存使用情况
  311. ax3 = plt.subplot2grid((4, 2), (1, 0), colspan=1)
  312. # 系统资源监控
  313. ax4 = plt.subplot2grid((4, 2), (1, 1), colspan=1)
  314. # 网络使用情况
  315. ax5 = plt.subplot2grid((4, 2), (2, 0), colspan=1)
  316. # 磁盘读写情况
  317. ax6 = plt.subplot2grid((4, 2), (2, 1), colspan=1)
  318. # 绘制各个子图
  319. plot_cprofile_table(ax1, data.get('analysis_results', {}).get('cprofile', {}))
  320. plot_cprofile_bar_chart(ax2, data.get('analysis_results', {}).get('cprofile', {}))
  321. plot_memory_data(ax3, data.get('analysis_results', {}).get('memory_profile', {}))
  322. plot_psutil_data(ax4, data.get('analysis_results', {}).get('psutil', {}))
  323. plot_network_data(ax5, data.get('analysis_results', {}).get('psutil', {}))
  324. plot_disk_io_data(ax6, data.get('analysis_results', {}).get('psutil', {}))
  325. # 调整布局
  326. plt.tight_layout()
  327. # 保存图像
  328. plt.savefig(args.output, dpi=300, bbox_inches='tight')
  329. print(f"性能分析图表已保存至: {args.output}")
  330. if __name__ == '__main__':
  331. main()