跳转至

JMeter 聚合报告指标解读:从 jtl 算出可信的结论

聚合报告只有十几个字段,但误读方式很多。这篇把字段、算法和判断标准拆开讲,并给出直接从 .jtl 复算的命令。

1. jtl 里到底有什么

非 GUI 模式默认生成 CSV:

timeStamp,elapsed,label,responseCode,responseMessage,threadName,dataType,success,failureMessage,bytes,sentBytes,grpThreads,allThreads,URL,Latency,IdleTime,Connect
字段 含义 常见误用
timeStamp 请求开始毫秒时间戳 机器时钟不同步时排序错乱
elapsed 从发出到收完响应的总耗时(含等待) 把它当“服务端处理时间”
Latency 首字节时间 与 elapsed 混用
Connect 建连耗时 短连接场景被忽略
success 断言与状态码综合结果 没写断言时恒为 true
responseCode HTTP 状态码 只看 200 不看业务码
bytes / sentBytes 响应/请求字节数 用来判断带宽时漏算 TLS 开销
allThreads 当前总线程数 与并发用户数不等价

2. 平均值为什么会骗人

100 个请求:95 个 50ms,5 个 5000ms。

平均 = (95×50 + 5×5000) / 100 = 297.5ms
P95  = 5000ms

平均值 297ms 看起来能接受,但每 20 个用户就有 1 个等了 5 秒。压测报告至少给 P90/P95/P99 + 错误率 + TPS。

P95 的定义:把所有样本按耗时升序排列,第 95% 位置的值。JMeter 聚合报告里的 90% Line 是同一算法。

3. 直接从 jtl 复算

用 Python 精确计算

import csv
from collections import defaultdict

p95 = defaultdict(list)
errors = defaultdict(int)
count = defaultdict(int)
elapsed_sum = defaultdict(float)

with open("result.jtl", newline="", encoding="utf-8") as f:
    for row in csv.DictReader(f):
        label = row["label"]
        e = int(row["elapsed"])
        p95[label].append(e)
        count[label] += 1
        elapsed_sum[label] += e
        if row["success"].lower() != "true":
            errors[label] += 1

for label in sorted(count, key=lambda k: -count[k]):
    vals = sorted(p95[label])
    idx = max(0, int(len(vals) * 0.95) - 1)
    avg = elapsed_sum[label] / count[label]
    err_rate = errors[label] / count[label] * 100
    print(f"{label:30s} n={count[label]:7d} avg={avg:8.1f}ms "
          f"P95={vals[idx]:8d}ms max={vals[-1]:8d}ms err={err_rate:.2f}%")

用 awk 快速看分布

awk -F, 'NR>1 {c[$3]++; s[$3]+=$2; if($2>m[$3]) m[$3]=$2} 
  END {for (l in c) printf "%-30s n=%-7d avg=%.1fms max=%dms\n", l, c[l], s[l]/c[l], m[l]}' \
  result.jtl | sort -k2 -t= -nr

看错误构成

awk -F, 'NR>1 && $8!="true" {print $4, $5}' result.jtl \
  | sort | uniq -c | sort -nr | head -20

先按 responseCode + responseMessage 分组,再决定是看服务端日志还是改脚本。

4. TPS 的三种口径

口径 计算 用途
总吞吐 样本数 / 总时长 概览,混合场景失真
成功吞吐 成功样本数 / 总时长 真实有效产能
峰值 TPS 按秒分桶取最大值 容量评估、限流阈值

按秒分桶:

import csv
from collections import Counter

per_sec = Counter()
with open("result.jtl", newline="", encoding="utf-8") as f:
    for row in csv.DictReader(f):
        if row["success"].lower() == "true":
            per_sec[int(row["timeStamp"]) // 1000] += 1

peak = max(per_sec.values())
avg = sum(per_sec.values()) / len(per_sec)
print(f"avg TPS={avg:.1f}, peak TPS={peak}")

错误吞吐必须单独说:TPS 1200、错误率 30%,有效 TPS 只有 840。把二者混在一起报,是在虚报容量。

5. 并发、TPS、RT 的关系

TPS = 并发数 / 平均RT(秒)

例:200 并发、平均 RT 250ms → 理论 800 TPS。

若实测只有 400 TPS,常见原因按优先级:

  1. 服务端线程池/连接池打满(看 allThreads 与 RT 同时上升)
  2. 数据库慢查询(慢日志)
  3. 压测机瓶颈(CPU、带宽、端口)
  4. 脚本里有固定等待或过重断言
  5. 中间有网关限流(429)

6. 判断“是否达标”的四条线

1. TPS ≥ 目标值
2. P95 ≤ SLA(如 500ms),P99 ≤ 2×P95
3. 错误率 < 0.1%,且无 5xx 集中出现
4. 服务端 CPU < 70%,内存无持续增长,GC 无频繁 Full GC

四条线要同时满足。只报 TPS 达标,可能在靠超时和重试堆出来的数字。

7. 常见误读清单

现象 真实原因
TPS 很平稳但 RT 缓慢上升 队列积压,系统在“扛”而不是“处理”
RT 双峰分布 有重试/超时(5s、10s 边界明显)
错误率 0 但业务成功率低 没写业务断言
聚合报告 TPS 高于按秒统计峰值 平均值掩盖了尖峰
Received KB/sec 很高 大响应体,带宽可能先于 CPU 成为瓶颈
加压到某点 TPS 不升、RT 陡增 到达容量拐点,继续加压只会增加排队

8. 报告模板

场景: 下单链路(浏览 60% / 加购 30% / 下单 10%)
负载: 900 线程,Ramp-Up 120s,持续 30min
结果: 有效 TPS 2140,P95 312ms,P99 880ms,错误率 0.02%
拐点: 1200 线程时 TPS 不再增长,P95 从 300ms 升至 1.2s
瓶颈: MySQL CPU 81%,订单查询未走索引
结论: 未达标(目标 2500 TPS),需优化 SQL 后复测

9. 验证方法

  • 用 Python 复算的 P95 与 HTML 报告误差 < 1%
  • 错误分类之和 = 总错误数
  • 成功吞吐 + 失败吞吐 = 总吞吐
  • 关闭断言再跑一次,对比 TPS(量化断言开销)

相关阅读