☰
Artillery 自定义指标跟踪实战:用 afterResponse 处理器统计请求计数与延迟直方图
2026/9/25 4:50:22 网站建设 项目流程
  • 性能测试
  • 接口测试
  • 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.

项目地址:https://gitcode.com/gh_mirrors/ar/artillery
点击查看免费下载

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.ymlArtillery 测试脚本,加载处理器并在场景级声明afterResponse钩子
metrics.js自定义处理器,发射pets_created计数器和pet_creation_latency直方图
package.json服务器依赖清单(express与server-timing)

运行链路分为两步:

  1. 安装并启动被测 API 服务器;
  2. 执行 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响应头解析为数值。实现逻辑:

  1. 按,分割得到多个计时段(若存在多个);
  2. 对每段再按;分割,timingDetails[0]是计时段名称;
  3. 找到与目标名称(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 自定义指标的标准套路:

  1. 被测端:接口输出可供解析的度量载体(本例为Server-Timing响应头);
  2. 配置端:在测试脚本的config.processor中加载处理器文件,并在场景级声明afterResponse钩子;
  3. 采集端:处理器函数内用events.emit('counter' | 'histogram', ...)上报业务指标;
  4. 呈现端:运行器把事件路由进指标聚合器,控制台与报告中以计数器和直方图形式呈现,与内置指标并列可读。

这套机制适用于任何"标准指标覆盖不到、但业务上需要度量"的场景,例如自定义业务计数、服务端分段耗时、外部依赖延迟等,是 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.

项目地址:https://gitcode.com/gh_mirrors/ar/artillery
点击查看免费下载

相关推荐

上一篇:MOTR完全指南:基于Transformer的端到端多目标跟踪框架详解
下一篇:EMO项目用户界面设计原则:打造直观的动态肖像生成体验

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询