GitLab CI 中如何把性能报告上传到 Merge Request 并可视化 diff
解读
在国内 GitLab 私有化部署(社区版/企业版)场景下,面试官真正想验证的是:
- 你是否能把“跑完脚本→拿到数据→让研发在 MR 里一眼看到差异”做成自动化闭环,而不是手动贴图;
- 是否熟悉 GitLab Artifacts、JUnit XML、Markdown Report、GitLab Pages 以及 GitLab 14.9+ 推出的 Performance Report 规范;
- 能否把性能指标(RT、TPS、CPU、内存、GC、DB 慢 SQL 等)抽象成可版本对比的“数值”,并用 GitLab 原生 Widget 或自定义 Markdown Table 展示 diff;
- 是否理解“MR 阶段只做增量快速验证,全量基准在 nightly pipeline”这一国内主流节奏,避免把重型压测搬进 MR 造成排队阻塞。
知识点
- GitLab CI 报表体系
- artifacts:reports:junit → 单元/接口测试,MR 的 Test Report Widget;
- artifacts:reports:performance → 14.9+ 专用,支持 load-performance.json 格式,MR 自动生成 Performance Report Widget;
- artifacts:reports:metrics → 15.1+ 支持任意 Prometheus Text 格式指标,MR 的 Metrics Report Widget;
- artifacts:reports:markdown → 任意 Markdown 片段,MR 的“Expand”区域展示,可自定义 diff table;
- 性能数据格式
- load-performance.json(GitLab 官方 schema,含 duration、requests、failedRequests、percentiles 等字段);
- JUnit XML(可把 SLA 断言包装成 testCase,失败即红线);
- Prometheus Exposition(0.0.4)文本,方便 metrics report 采集;
- diff 思路
- 基线数据持久化:把 master 分支最近一次 green pipeline 的 load-performance.json 存成 GitLab Pages 或对象存储,供 MR pipeline 下载;
- 数值 diff:jq 或 Python 脚本对比基线与当前,计算 Δ%、显著性标记(↑↓→);
- 可视化:生成 Markdown Table 或 load-performance.json(只保留 diff 行),再上传到 reports:markdown / reports:performance;
- 国内常见坑
- 社区版无 Performance Widget,需降级用 reports:markdown + Pages;
- 大文件 artifacts 默认 1G 上限,压测原始日志需二次聚合后再上传;
- 多子系统时,需把网关、服务 A、服务 B 的指标合并到同一 load-performance.json,否则 MR 只会展示第一个文件;
- 私有化实例若关闭 Pages,可把基线 json 存到同一仓库的 gh-pages 分支(GitLab 也能托管)或内部 MinIO。
答案
以 JMeter + 企业版 GitLab 14.10 为例,给出可直接落地的 .gitlab-ci.yml 片段,实现“MR 自动展示性能 diff”。
variables:
BASELINE_URL: "https://pages.gitlab.example.com/${CI_PROJECT_PATH}/baseline/load-performance.json"
PERF_WORKSPACE: "$CI_PROJECT_DIR/perf"
stages:
- perf_test
- perf_report
# 1. 运行压测并生成 load-performance.json
perf:mr:
stage: perf_test
image: harbor.example.com/qa/jmeter:5.6
only:
- merge_requests
script:
- mkdir -p $PERF_WORKSPACE
# 拉取基线
- curl -s -o $PERF_WORKSPACE/baseline.json "$BASELINE_URL" || echo "no baseline, skip"
# 执行 5 min 负载,只跑核心场景(登录+下单)
- jmeter -n -t scripts/mr.jmx -l $PERF_WORKSPACE/result.jtl -e -o $PERF_WORKSPACE/site
# 用 python 脚本把 jtl 转成 GitLab 官方格式
- python3 scripts/jtl2gitlab.py $PERF_WORKSPACE/result.jtl $PERF_WORKSPACE/current.json
# 生成 diff markdown
- python3 scripts/diff.py $PERF_WORKSPACE/baseline.json $PERF_WORKSPACE/current.json > $PERF_WORKSPACE/diff.md
artifacts:
when: always
paths:
- perf/site/ # 详细 HTML 报告,供 Pages 长期存档
reports:
junit: perf/perf.xml # 把 SLA 断言变成 testCase
performance: perf/current.json # MR 的 Performance Widget
markdown: perf/diff.md # 自定义 diff table
cache:
key: jmeter-plugins
paths:
- ~/.jmeter/
# 2. 把当前结果同步为新的基线(只有合并后才更新)
perf:baseline:
stage: perf_report
image: alpine
only:
- master
script:
- cp perf/current.json public/load-performance.json
artifacts:
paths:
- public
expire_in: 90 day
cache: {}
配套脚本要点
- jtl2gitlab.py:把 JMeter 聚合报告转换成 GitLab load-performance.json 格式,关键字段
- duration、requests、failedRequests、percentiles{“95”:xxx};
- diff.py:
- 读取 baseline.json 与 current.json,计算 Δ%;
- 若 95th RT 上涨超过 20% 或 TPS 下降超过 10%,在表格里用↑↓高亮;
- 输出 Markdown 表格,上限 30 行,避免 MR 页面过长;
- SLA 断言:
- 在 JMeter 中断言 95th RT < 800 ms、Error < 0.5%,失败时把 case 写入 perf.xml,MR 即现红叉,阻止合并。
效果
- 研发在 MR 页面右侧看到“Performance”标签,点开即是 95th、99th、TPS、Error 的当前值与基线 diff;
- 同时“Expand”区域显示 markdown 表格,一目了然哪条 API 退化;
- 若指标超限,JUnit 红线直接阻断合并,倒逼代码在 MR 阶段就完成性能回退修复。
拓展思考
- 多环境基准:国内很多团队把 nightly 跑在“等比缩容”K8s 环境,而 MR 跑在“容器 1C2G”容器,二者水位不同。解决方法是把基线按“CPU 核数”归一化,diff 时统一折算成“RT/核”或“TPS/核”,避免误报警。
- 大促模型复用:把双 11 模型拆成“读多”“写多”“秒杀”三类子脚本,MR 阶段只跑“读多”10 分钟,nightly 再跑全量。通过 GitLab CI
rules:changes感知代码路径,自动选择对应子脚本,既快又准。 - 自定义指标:除了 RT/TPS,还可把 GC 次数、慢 SQL 条数、Redis 命中率也写进 load-performance.json 的 extendedMetrics 字段,GitLab 15+ 的 Performance Widget 会自动展开,真正做到“代码未动,指标先现”。
- 社区版 fallback:若公司只有社区版,可关闭 performance report,改用 artifacts:reports:markdown + GitLab Pages,把 diff.md 渲染成静态站点,在 MR 描述里用机器人账号自动评论链接,同样实现“可视化 diff”效果。