- 性能测试
- 接口测试
- CLI
【免费下载链接】artillery
The complete load testing platform. Everything you need for production-grade load tests. Serverless & distributed. Load test with Playwright. Load test HTTP APIs, GraphQL, WebSocket, and more. Use any Node.js module.
Artillery 内置的 HTTP 引擎在每次请求后都会自动记录http.response_time、http.requests、http.codes.*等标准指标,但业务级别的度量(例如"宠物创建请求数"、"某一服务端计时段落的耗时")并不在默认采集范围内。本篇指南以仓库中的 examples/track-custom-metrics 示例为骨架,讲解如何通过自定义 JS 处理器(processor)配合afterResponse钩子,利用events事件总线发射自定义counter与histogram指标,并将其与直方图一起输出到 Artillery 的测试报告。读完本文,你将掌握自定义指标从"定义 → 采集 → 聚合 → 报告呈现"的完整链路。
示例结构与运行流程
examples/track-custom-metrics目录是一个完整可运行的闭环示例,包含以下文件:
| 文件 | 作用 |
|---|---|
| app.js | 被测 API 服务器(Express),提供POST /pets接口并输出Server-Timing响应头 |
| custom-metrics.yml | Artillery 测试脚本,加载处理器并在场景级声明afterResponse钩子 |
| metrics.js | 自定义处理器,发射pets_created计数器和pet_creation_latency直方图 |
| package.json | 服务器依赖清单(express与server-timing) |
运行链路分为两步:
- 安装并启动被测 API 服务器;
- 执行 Artillery 测试脚本,让虚拟用户按
arrivalRate持续向/pets发起 POST 请求,每收到一个响应即触发afterResponse钩子收集自定义指标。
第一步:启动被测 API 服务器
先安装服务器依赖:
npm install该命令会安装 package.json 中声明的两个依赖:
express(^4.17.1):提供 HTTP 服务;server-timing(^3.3.1):中间件,负责生成 W3C 规范的Server-Timing响应头。
然后启动服务器:
node app.js服务启动后监听在http://localhost:3000/。
查看 app.js 的源码,可以看到被测接口的实现要点:
const express = require('express'); const serverTiming = require('server-timing'); const app = express(); const port = 3000; app.use(express.json()); app.use(serverTiming()); app.post('/pets', (req, res) => { res.startTime('pets', 'Creating pet'); setTimeout( () => { res.endTime('pets'); res.json({ species: req.body.species, name: req.body.name }); }, Math.ceil(Math.random() * 500) ); }); app.listen(port, () => { console.log(`App listening at http://localhost:${port}`); });这个接口刻意在 0~500ms 之间随机延迟后返回,从而为后续的延迟直方图提供有分布的样本数据。关键点在于:
res.startTime('pets', 'Creating pet')开始记录名为pets的计时段;res.endTime('pets')结束计时,server-timing中间件据此在响应头中输出类似pets;dur=xxx的Server-Timing头。
这份 Server-Timing 头正是处理器解析延迟数据的来源,也是"服务端计时与负载测试打通"的巧妙设计。
第二步:理解测试脚本 custom-metrics.yml
测试脚本 custom-metrics.yml 是整个示例的核心配置:
config: target: "http://localhost:3000" processor: "./metrics.js" phases: - arrivalRate: 25 duration: 60 scenarios: - afterResponse: "trackPets" flow: - post: url: "/pets" json: species: "pony" name: "Tiki"逐项拆解其含义:
config.target:所有请求的基准地址,场景中url: "/pets"会拼接为http://localhost:3000/pets;config.processor:声明处理器文件路径。Artillery 会加载该文件并将其导出的函数挂到config.processor对象上,供场景钩子按函数名引用;config.phases:负载阶段定义。arrivalRate: 25表示每秒新启动 25 个虚拟用户,duration: 60表示持续 60 秒,合计约 1500 个虚拟用户向服务器发起请求;scenarios[0].afterResponse: "trackPets":场景级响应后钩子,在 HTTP 引擎每收到一个响应后调用名为trackPets的处理器函数;scenarios[0].flow:虚拟用户执行的请求序列,这里仅包含一个POST /pets请求,携带 JSON 请求体。
从引擎源码看,afterResponse既可以在场景级声明(如本例),也支持在单个请求步骤内通过requestParams.afterResponse声明。在 packages/artillery/lib/core/engine_http.ts 中,两者会被合并后按顺序逐个调用:
const functionNames = _.concat( opts?.afterResponse || [], params.afterResponse || [] ); async.eachSeries( functionNames, function iteratee(functionName: string, next) { const fn = template(functionName, context); let processFunc = config.processor?.[fn]; // ...逐个执行 }, // ... );值得注意的细节:
- 处理器通过函数名(字符串)从
config.processor中按名查找,因此metrics.js必须module.exports导出名为trackPets的函数; - 若在处理器中找不到对应函数,引擎会打印
WARNING: custom function <name> could not be found并以空回调兜底,不会中断测试(见 engine_http.ts 的 TODO 分支); - 处理器既支持同步函数(
(req, res, context, events, done)风格),也支持返回 Promise 的异步函数,引擎会根据processFunc.constructor.name判断调用方式(engine_http.ts); - 引擎只有在场景或请求中声明了
capture、match或afterResponse时才会进入响应处理流程(needToProcessResponse判断,见 engine_http.ts),这也解释了为什么自定义指标采集必须显式声明钩子。
第三步:编写自定义指标处理器 metrics.js
处理器 metrics.js 是本示例的"业务指标采集器":
module.exports = { trackPets }; function trackPets(_req, res, _context, events, done) { // After every response, increment the 'pets_created' counter by 1. events.emit('counter', 'pets_created', 1); // Parse the 'server-timing' header and look for the 'pets' metric, // and add it to 'pet_creation_latency' histogram. const latency = parseServerTimingLatency( res.headers['server-timing'], 'pets' ); events.emit('histogram', 'pet_creation_latency', latency); return done(); } function parseServerTimingLatency(header, timingMetricName) { const serverTimings = header.split(','); for (const timing of serverTimings) { const timingDetails = timing.split(';'); if (timingDetails[0] === timingMetricName) { return parseFloat(timingDetails[1].split('=')[1]); } } }处理器函数签名
afterResponse钩子的处理器统一接收五个参数:
| 参数 | 含义 |
|---|---|
requestParams | 本次请求的参数(URL、请求体等),本例未使用,故命名为_req |
res | 响应对象,包含headers、body、statusCode等字段。引擎在调用钩子前会主动把响应体挂到res.body(见 engine_http.ts) |
context | 虚拟用户上下文,包含vars变量表,可读写跨请求共享的变量 |
events | 场景事件发射器,自定义指标通过它上报 |
done | 回调函数,处理器完成工作后必须调用以继续场景流程 |
计数器:events.emit('counter', ...)
events.emit('counter', 'pets_created', 1);每次响应到达即累加 1,用于统计测试期间总共创建了多少只宠物。这个自定义计数器与引擎内置计数器走的是同一条数据通路——在 packages/artillery/lib/core/runner.ts 中,运行器监听了scenarioEvents上的counter事件并转发给指标聚合器:
runState.scenarioEvents = new EventEmitter(); runState.scenarioEvents.on('counter', (name: string, value: number) => { metrics.counter(name, value); });引擎内部对http.requests、http.responses、http.codes.*的统计也是通过同样的ee.emit('counter', ...)完成的(见 engine_http.ts),这说明自定义计数器与内置指标共享同一套聚合、取样与报告机制。
直方图:events.emit('histogram', ...)
events.emit('histogram', 'pet_creation_latency', latency);把从响应头解析出的延迟值加入pet_creation_latency直方图。运行器将histogram事件路由到metrics.summary(name, value)(见 runner.ts),因此该指标在报告中会以"直方图"形态呈现——包含 min / max / mean / median / p95 / p99 等统计值,与内置的http.response_time展示方式一致。引擎自身对响应时间、DNS、TCP、TLS 等分段耗时的记录同样走ee.emit('histogram', ...)通道(见 engine_http.ts),自定义直方图可以与之并列查看。
解析 Server-Timing 头
parseServerTimingLatency是一个纯函数,负责把形如:
pets;dur=123.45的Server-Timing响应头解析为数值。实现逻辑:
- 按
,分割得到多个计时段(若存在多个); - 对每段再按
;分割,timingDetails[0]是计时段名称; - 找到与目标名称(
pets)匹配的段,从timingDetails[1](形如dur=123.45)中取出=后的数值并parseFloat。
这一做法把服务端框架(如 Express 的server-timing中间件)暴露的计时信息"桥接"进负载测试指标,是打通前后端可观测性的一个轻量范例。实际项目中,你也可以从其他响应头、响应体或 HTTP 状态码中提取任意业务数据作为自定义指标来源。
第四步:运行测试并查看自定义指标
被测服务器运行在http://localhost:3000/后,在示例目录下执行:
artillery run custom-metrics.yml测试按arrivalRate: 25 / duration: 60的相位运行 60 秒。期间每个响应都会触发trackPets,累计发射约 1500 次counter事件和等量的histogram事件。
报告中的呈现方式
在报告输出端,控制台报告器会分别渲染三类自定义指标(见 packages/artillery/lib/console-reporter.ts):
- 计数器(
pets_created)走printCounters,输出形如pets_created: 1500的单值行; - 直方图(
pet_creation_latency)走printSummaries,输出min / max / mean / median / p95 / p99六项统计值(console-reporter.ts)。
可以预期,报告会同时出现:
- 内置指标:
http.codes.200、http.requests、http.response_time等; - 自定义指标:
pets_created(计数器)与pet_creation_latency(直方图)。
其中pet_creation_latency的分布应大致对应服务端 0~500ms 的随机延迟,可用于验证服务端处理耗时与客户端观测延迟的一致性。
关于采样与汇总
Artillery 的指标系统以固定间隔对计数器/直方图做采样聚合(相关数据模型见 packages/artillery/lib/core/ssms.ts 中customStats字段的设计)。自定义指标与内置指标一样参与这一机制,因此它们也会出现在 JSON 报告(-o输出)与 HTML 报告中,方便后续用artillery report或外部工具做离线分析。
自定义指标机制的扩展视角
除本示例展示的两类事件外,运行器还为自定义处理器暴露了更多事件通道(见 runner.ts):
| 事件 | 语义 | 对应聚合方式 |
|---|---|---|
counter | 计数累加 | metrics.counter(name, value) |
histogram | 数值分布统计 | metrics.summary(name, value) |
summary | 同上,直方图别名 | metrics.summary(name, value) |
rate | 每秒速率 | metrics.rate(name) |
customStat | 旧版自定义统计(已标记弃用) | metrics.summary(stat.stat, stat.value) |
error | 上报错误码 | metrics.counter('errors.<code>', 1) |
这意味着同一个处理器函数不仅能做"计数 + 直方图",还能上报每秒速率(如"每秒宠物创建量")或错误统计。自定义指标与内置指标共用同一报告管线,因此无论是控制台实时输出、JSON 导出还是 HTML 报告,自定义指标都能无缝纳入。
小结
通过examples/track-custom-metrics这个完整示例,可以看到 Artillery 自定义指标的标准套路:
- 被测端:接口输出可供解析的度量载体(本例为
Server-Timing响应头); - 配置端:在测试脚本的
config.processor中加载处理器文件,并在场景级声明afterResponse钩子; - 采集端:处理器函数内用
events.emit('counter' | 'histogram', ...)上报业务指标; - 呈现端:运行器把事件路由进指标聚合器,控制台与报告中以计数器和直方图形式呈现,与内置指标并列可读。
这套机制适用于任何"标准指标覆盖不到、但业务上需要度量"的场景,例如自定义业务计数、服务端分段耗时、外部依赖延迟等,是 Artillery HTTP 压测中做精细化可观测性的基础能力。
- 性能测试
- 接口测试
- CLI
【免费下载链接】artillery
The complete load testing platform. Everything you need for production-grade load tests. Serverless & distributed. Load test with Playwright. Load test HTTP APIs, GraphQL, WebSocket, and more. Use any Node.js module.
相关推荐
shadow-rs 最佳实践:10 个提升 Rust 项目质量的实用技巧
shadow rs 最佳实践:10 个提升 Rust 项目质量的实用技巧 shadow rs 是一款在编译期自动收集构建信息的 Rust 工具,它能把 Git
开发工具localtunnel请求延迟优化:CDN与边缘计算全指南
localtunnel请求延迟优化:CDN与边缘计算全指南 引言:解决本地开发的远程访问痛点 你是否遇到过使用localtunnel进行远程测试时,因请求延迟过
网络开发工具后端MediaPipe 数据集实战:3 步把散图变成能训练的完整数据集
MediaPipe 数据集实战:3 步把散图变成能训练的完整数据集 装标注工具装到一半报错、导出的 XML 和代码对不上、格式转换折腾一下午——这些事我都被坑过
人工智能机器学习计算机视觉多模态本地部署
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考