main.sh 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. #!/bin/bash
  2. # 性能测试脚本,用于顺序执行多种性能分析工具
  3. # 颜色定义
  4. RED='\033[0;31m'
  5. GREEN='\033[0;32m'
  6. YELLOW='\033[1;33m'
  7. BLUE='\033[0;34m'
  8. NC='\033[0m' # No Color
  9. # 默认参数
  10. RESULT_DIR="results"
  11. TEST_SCRIPT="" # 待测试的脚本
  12. PSUTIL_SCRIPT="monitor_util.py" # 监测工具脚本
  13. INTEGRATED_SCRIPT="integrate_results.py" # 融合工具脚本
  14. PLOT_SCRIPT="plot.py" # 画图工具脚本
  15. CPROFILE_OUTPUT="$RESULT_DIR/cprofile_output.prof" # cProfile 结果文件
  16. MEMORY_PROFILE_OUTPUT="$RESULT_DIR/mprofile_output.dat" # memory_profiler输出文件
  17. MEMORY_PLOT_OUTPUT="$RESULT_DIR/mprofile_memory_plot.png" # memory_profiler 绘制的曲线图
  18. FLAMEGRAPH_OUTPUT="$RESULT_DIR/pyspy_output.svg" # py-spy 输出文件
  19. PSUTIL_OUTPUT="$RESULT_DIR/psutil_output.csv" # psutil 输出文件
  20. INTEGRATED_REPORT="$RESULT_DIR/performance_analysis_report.json" # 融合结果文件
  21. INTEGRATED_PLOT="$RESULT_DIR/performance_analysis_report.png" # 融合结果图
  22. # 日志函数
  23. log_info() {
  24. echo -e "${BLUE}[INFO]${NC} $1"
  25. }
  26. log_success() {
  27. echo -e "${GREEN}[SUCCESS]${NC} $1"
  28. }
  29. log_warning() {
  30. echo -e "${YELLOW}[WARNING]${NC} $1"
  31. }
  32. log_error() {
  33. echo -e "${RED}[ERROR]${NC} $1"
  34. }
  35. # 显示标题
  36. show_header() {
  37. echo "========================================="
  38. echo "Python脚本性能分析工具链"
  39. echo "========================================="
  40. sleep 2
  41. }
  42. # 显示步骤标题
  43. show_step() {
  44. echo ""
  45. echo "-----------------------------------------"
  46. echo "$1"
  47. echo "-----------------------------------------"
  48. }
  49. # 检查脚本文件是否存在
  50. check_script_exists() {
  51. if [ -f "config.json" ]; then
  52. TEST_SCRIPT=$(python -c "
  53. import json
  54. try:
  55. with open('config.json', 'r') as f:
  56. config = json.load(f)
  57. print(config.get('test_script_dir', ''))
  58. except Exception as e:
  59. print('')
  60. " 2>/dev/null)
  61. if [ -n "$TEST_SCRIPT" ]; then
  62. log_info "从 config.json 读取测试脚本路径: $TEST_SCRIPT"
  63. else
  64. log_warning "config.json 中未找到有效的 test_script_dir 配置"
  65. return 1
  66. fi
  67. else
  68. log_warning "未找到 config.json 配置文件"
  69. return 1
  70. fi
  71. if [ ! -f "$TEST_SCRIPT" ]; then
  72. log_error "测试脚本 $TEST_SCRIPT 不存在"
  73. return 1
  74. fi
  75. sleep 2
  76. return 0
  77. }
  78. # 创建结果保存目录
  79. mk_dir(){
  80. if [ ! -d "$RESULT_DIR" ]; then
  81. mkdir -p "$RESULT_DIR"
  82. log_info "创建结果保存目录: $RESULT_DIR"
  83. else
  84. log_info "使用现有结果保存目录: $RESULT_DIR"
  85. fi
  86. }
  87. # 1. 使用cProfile进行性能分析
  88. run_cprofile() {
  89. show_step "步骤 1: 使用 cProfile 进行性能分析"
  90. log_info "准备对 $TEST_SCRIPT 进行性能分析..."
  91. # 将之前的结果删除
  92. rm -rf "$CPROFILE_OUTPUT"
  93. if python -m cProfile -o "$CPROFILE_OUTPUT" "$TEST_SCRIPT"; then
  94. log_success "cProfile 分析完成,结果保存至 $CPROFILE_OUTPUT"
  95. return 0
  96. else
  97. log_error "cProfile 分析失败"
  98. return 1
  99. fi
  100. }
  101. # 3. 使用memory_profiler分析内存使用情况
  102. run_memory_profiler() {
  103. show_step "步骤 3: 使用 memory_profiler 分析内存使用情况"
  104. # 检查是否安装了memory_profiler
  105. if ! command -v mprof &> /dev/null; then
  106. log_warning "未找到 memory_profiler (mprof),尝试安装..."
  107. if pip3 install memory-profiler -i https://pypi.tuna.tsinghua.edu.cn/simple; then
  108. log_success "memory_profiler 安装成功"
  109. echo "适配安装matplotlib: "
  110. conda install matplotlib
  111. else
  112. log_error "memory_profiler 安装失败"
  113. return 1
  114. fi
  115. fi
  116. # 将之前的结果删除
  117. rm -rf "$MEMORY_PROFILE_OUTPUT"
  118. # 运行内存分析
  119. if command -v mprof &> /dev/null; then
  120. if mprof run --output "$MEMORY_PROFILE_OUTPUT" "$TEST_SCRIPT"; then
  121. log_success "memory_profiler 分析完成,结果保存至 $MEMORY_PROFILE_OUTPUT"
  122. # 绘制内存占用曲线(弃用)
  123. # log_info "绘制内存占用曲线"
  124. # if MPLBACKEND=Agg mprof plot --flame "$MEMORY_PROFILE_OUTPUT" -o "$MEMORY_PLOT_OUTPUT"; then
  125. # log_success "绘制完成,结果保存至 $MEMORY_PLOT_OUTPUT"
  126. # else
  127. # log_error "绘制失败"
  128. # fi
  129. else
  130. log_error "memory_profiler 分析失败"
  131. fi
  132. else
  133. log_warning "无法安装 memory_profiler,跳过内存分析步骤"
  134. fi
  135. }
  136. # 2. 使用py-spy记录性能数据
  137. run_py_spy() {
  138. show_step "步骤 2: 使用 py-spy record 进行性能分析"
  139. # 检查是否安装了py-spy
  140. if ! command -v py-spy &> /dev/null; then
  141. log_warning "未找到 py-spy ,尝试安装..."
  142. if pip3 install py-spy -i https://pypi.tuna.tsinghua.edu.cn/simple; then
  143. log_success "py-spy 安装成功"
  144. else
  145. log_error "py-spy 安装失败"
  146. return 1
  147. fi
  148. fi
  149. # 再次检查是否安装成功
  150. if ! command -v py-spy &> /dev/null; then
  151. log_error "py-spy 安装失败"
  152. return 1
  153. fi
  154. # 删除之前的结果
  155. rm -rf "$FLAMEGRAPH_OUTPUT"
  156. # 根据操作系统类型选择合适的py-spy参数,需要统计子线程和C拓展
  157. if [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]]; then
  158. # Windows环境下使用--nonblocking选项
  159. log_info "Windows环境下使用--nonblocking选项"
  160. PYSPY_CMD="py-spy record -o $FLAMEGRAPH_OUTPUT --duration 35 --native --subprocesses --nonblocking -- python $TEST_SCRIPT"
  161. elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
  162. # Linux环境下使用sudo运行
  163. log_info "Linux环境下使用sudo运行py-spy"
  164. PYSPY_CMD="sudo env \"PATH=\$PATH\" py-spy record -o $FLAMEGRAPH_OUTPUT --duration 35 --native --subprocesses -- python $TEST_SCRIPT"
  165. else
  166. # 其他Unix系统使用默认选项
  167. PYSPY_CMD="py-spy record -o $FLAMEGRAPH_OUTPUT --duration 35 --native --subprocesses -- python $TEST_SCRIPT"
  168. fi
  169. log_info "执行命令: $PYSPY_CMD"
  170. eval "$PYSPY_CMD"
  171. PYSPY_PID=$!
  172. if wait $PYSPY_PID; then
  173. log_success "py-spy record 分析完成,火焰图保存至 $FLAMEGRAPH_OUTPUT"
  174. return 0
  175. else
  176. log_error "py-spy record 分析失败"
  177. if [[ "$OSTYPE" == "linux-gnu"* ]]; then
  178. log_warning "提示: 可能需要管理员权限,请使用 'sudo ./$0' 运行此脚本"
  179. fi
  180. return 1
  181. fi
  182. }
  183. # 4. 使用psutil获取进程信息
  184. run_psutil() {
  185. show_step "步骤 4: 使用 psutil 获取进程信息"
  186. # 检查是否安装了psutil
  187. if ! python -c "import psutil" &> /dev/null; then
  188. log_warning "未找到 psutil ,尝试安装..."
  189. if pip3 install psutil -i https://pypi.tuna.tsinghua.edu.cn/simple; then
  190. log_success "psutil 安装成功"
  191. else
  192. log_error "psutil 安装失败"
  193. return 1
  194. fi
  195. fi
  196. # 将之前的结果文件删除
  197. rm -rf "$PSUTIL_OUTPUT"
  198. # 在后台运行test_script.py并获取其PID
  199. log_info "在后台启动 $TEST_SCRIPT ..."
  200. python "$TEST_SCRIPT" &
  201. TEST_PID=$!
  202. # 等待一段时间确保进程已经完全启动
  203. sleep 2
  204. # 验证进程是否仍在运行
  205. if ! kill -0 $TEST_PID 2>/dev/null; then
  206. log_error "测试脚本启动失败"
  207. return 1
  208. fi
  209. log_info "测试脚本 PID: $TEST_PID"
  210. # 使用monitor.py监控该进程
  211. log_info "开始监控进程资源使用情况..."
  212. if [[ "$OSTYPE" == "linux-gnu"* ]]; then
  213. # Linux环境下使用sudo运行monitor.py
  214. sudo env "PATH=$PATH" python "$PSUTIL_SCRIPT" --pid $TEST_PID --output "$PSUTIL_OUTPUT" &
  215. else
  216. python "$PSUTIL_SCRIPT" --pid $TEST_PID --output "$PSUTIL_OUTPUT" &
  217. fi
  218. MONITOR_PID=$!
  219. # 等待test_script.py执行完成
  220. wait $TEST_PID
  221. sleep 4
  222. log_success "测试脚本执行完成"
  223. # 终止monitor.py
  224. kill $MONITOR_PID 2>/dev/null || true
  225. log_success "资源监控完成,结果保存至 $PSUTIL_OUTPUT"
  226. }
  227. # 自动打开SVG文件
  228. open_svg_file() {
  229. if [ -f "$FLAMEGRAPH_OUTPUT" ]; then
  230. log_info "正在尝试在浏览器中打开火焰图..."
  231. # 检测是否在WSL环境中
  232. if grep -qE "(Microsoft|WSL)" /proc/version 2>/dev/null; then
  233. # WSL环境 - 使用PowerShell打开
  234. log_info "检测到WSL环境,使用PowerShell打开文件"
  235. if powershell.exe -Command "Start-Process '$(wslpath -w "$(pwd)/$FLAMEGRAPH_OUTPUT")'" 2>/dev/null; then
  236. log_success "SVG文件已在Windows默认应用中打开"
  237. return 0
  238. elif cmd.exe /c "start $(wslpath -w "$(pwd)/$FLAMEGRAPH_OUTPUT")" 2>/dev/null; then
  239. log_success "SVG文件已在Windows默认应用中打开"
  240. return 0
  241. else
  242. log_warning "无法在Windows主机上打开文件,请手动打开 $FLAMEGRAPH_OUTPUT"
  243. fi
  244. elif command -v xdg-open >/dev/null 2>&1; then
  245. # 标准Linux环境
  246. if xdg-open "$FLAMEGRAPH_OUTPUT"; then
  247. log_success "SVG文件已在默认应用中打开"
  248. return 0
  249. else
  250. log_warning "无法使用xdg-open打开文件"
  251. fi
  252. elif command -v gnome-open >/dev/null 2>&1; then
  253. if gnome-open "$FLAMEGRAPH_OUTPUT"; then
  254. log_success "SVG文件已在默认应用中打开"
  255. return 0
  256. else
  257. log_warning "无法使用gnome-open打开文件"
  258. fi
  259. else
  260. log_warning "找不到合适的打开命令,请手动在浏览器中打开 $FLAMEGRAPH_OUTPUT"
  261. fi
  262. fi
  263. return 1
  264. }
  265. # 自动打开prof文件
  266. open_prof_file() {
  267. # 检查snakeviz是否存在,不存在就尝试自动安装
  268. if [ -f "$CPROFILE_OUTPUT" ]; then
  269. log_info "自动打开cProfile分析结果"
  270. if ! command -v snakeviz &> /dev/null; then
  271. log_warning "未找到snakeviz,尝试安装..."
  272. if pip3 install snakeviz -i https://pypi.tuna.tsinghua.edu.cn/simple; then
  273. log_success "snakeviz 安装成功"
  274. else
  275. log_error "snakeviz 安装失败"
  276. return 1
  277. fi
  278. fi
  279. if command -v snakeviz &> /dev/null; then
  280. log_info "正在尝试使用snakeviz打开prof文件..."
  281. if snakeviz "$CPROFILE_OUTPUT"; then
  282. log_success "prof文件浏览完成"
  283. return 0
  284. else
  285. log_warning "无法使用snakeviz打开prof文件"
  286. fi
  287. else
  288. log_warning "snakeviz未安装,请手动打开 $CPROFILE_OUTPUT"
  289. fi
  290. else
  291. log_error "$CPROFILE_OUTPUT 文件不存在"
  292. fi
  293. return 1
  294. }
  295. # 显示结果摘要
  296. show_summary() {
  297. show_step "性能分析结果摘要"
  298. echo "1. cProfile 输出: $CPROFILE_OUTPUT (可用于 pstats 分析)"
  299. echo "2. py-spy 火焰图: $FLAMEGRAPH_OUTPUT (可在浏览器中打开查看)"
  300. echo "3. memory_profiler 输出: $MEMORY_PROFILE_OUTPUT (内存使用情况)"
  301. echo "4. psutil 输出: $PSUTIL_OUTPUT (硬件资源使用情况)"
  302. log_success "性能分析工具链执行完毕!"
  303. }
  304. # 整合所有分析结果
  305. integrate_all_results(){
  306. show_step "步骤5 整合所有分析结果"
  307. # 检查python脚本是否存在
  308. if [ ! -f "$INTEGRATED_SCRIPT" ]; then
  309. log_warning "整合脚本 $INTEGRATED_SCRIPT 不存在"
  310. return 1
  311. else
  312. echo "结果整合中..."
  313. # 构建命令参数
  314. CMD="python $INTEGRATED_SCRIPT"
  315. if [ -f "$CPROFILE_OUTPUT" ]; then
  316. CMD="$CMD --cprofile $CPROFILE_OUTPUT"
  317. fi
  318. if [ -f "$MEMORY_PROFILE_OUTPUT" ]; then
  319. CMD="$CMD --memory $MEMORY_PROFILE_OUTPUT"
  320. fi
  321. if [ -f "$FLAMEGRAPH_OUTPUT" ]; then
  322. CMD="$CMD --flamegraph $FLAMEGRAPH_OUTPUT"
  323. fi
  324. if [ -f "$PSUTIL_OUTPUT" ]; then
  325. CMD="$CMD --psutil $PSUTIL_OUTPUT"
  326. fi
  327. CMD="$CMD --output $INTEGRATED_REPORT"
  328. # 执行整合脚本
  329. log_info "执行整合命令: $CMD"
  330. if eval "$CMD"; then
  331. log_success "所有分析结果已整合至 $INTEGRATED_REPORT"
  332. else
  333. log_error "整合过程出错"
  334. # 检查pandas是否存在
  335. if ! python -c "import pandas" &> /dev/null; then
  336. log_warning "未找到pandas,尝试安装..."
  337. if pip3 install pandas -i https://pypi.tuna.tsinghua.edu.cn/simple; then
  338. log_success "pandas 安装成功"
  339. else
  340. log_error "pandas 安装失败"
  341. return 1
  342. fi
  343. # 尝试再次执行命令
  344. if eval "$CMD"; then
  345. log_success "所有分析结果已整合至 $INTEGRATED_REPORT"
  346. else
  347. log_error "无法执行整合命令"
  348. return 1
  349. fi
  350. else
  351. return 1
  352. fi
  353. fi
  354. # 绘制融合结果图
  355. if [ -f "$PLOT_SCRIPT" ]; then
  356. log_info "绘制融合结果图"
  357. if python "$PLOT_SCRIPT" --input "$INTEGRATED_REPORT" --output "$INTEGRATED_PLOT"; then
  358. log_success "融合结果图已保存至 $INTEGRATED_PLOT"
  359. else
  360. log_error "无法绘制融合结果图"
  361. return 1
  362. fi
  363. else
  364. log_warning "未找到绘制脚本 $PLOT_SCRIPT"
  365. return 1
  366. fi
  367. fi
  368. return 0
  369. }
  370. # 主函数
  371. main() {
  372. # 显示标题
  373. show_header
  374. # 检查脚本是否存在
  375. if ! check_script_exists; then
  376. log_error "请检查测试脚本是否存在."
  377. exit 1
  378. fi
  379. # 创建结果保存目录
  380. mk_dir
  381. # 执行各项分析
  382. run_cprofile # cProfile 算法性能分析,统计主线程的函数调用次数和运行时间
  383. run_py_spy # py-spy 火焰图
  384. run_memory_profiler # 内存使用情况分析
  385. run_psutil # 使用psutil监控进程资源使用情况
  386. show_summary # 显示结果摘要
  387. integrate_all_results # 整合所有结果
  388. # 自动打开结果文件
  389. open_svg_file
  390. open_prof_file
  391. }
  392. # 执行主函数
  393. main "$@"