news 2026/9/25 2:50:23

Artillery 自定义插件开发实战:以 artillery-plugin-hello-world 为例剖析插件接口与扩展机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Artillery 自定义插件开发实战:以 artillery-plugin-hello-world 为例剖析插件接口与扩展机制
  • 性能测试
  • 接口测试
  • 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
点击查看免费下载

本指南以仓库内 examples/artillery-plugin-hello-world 示例插件为主线,完整讲解 Artillery 的插件接口(v1/v2 两种形态)、插件包的命名与加载约定、如何读取测试脚本配置、如何为所有场景挂载自定义请求钩子(beforeRequest)、如何通过事件机制上报自定义计数器,以及插件的清理钩子(cleanup)。读完本文,你将具备从零编写一个可被 Artillery 自动发现、加载并生效的 Node.js 插件的能力,并理解插件从解析到实例化的底层调用链。

一、这个示例插件展示了什么

artillery-plugin-hello-world是一个刻意保持“最小可用”的 Artillery 插件,其 README 明确列出了它要演示的四件事:

  1. Artillery 的插件接口长什么样;
  2. 一个骨架级(barebones)插件是如何构建起来的;
  3. 如何在插件内部读取测试脚本(test script)的属性;
  4. 如何为场景挂载自定义 hook,从而在压测过程中做一些“有趣”的事。

换句话说,它不依赖任何外部服务,也不实现复杂的指标采集,而是把插件开发的最小完整闭环——定义、加载、配置读取、钩子注入、事件上报、清理——一次性走通,是理解 Artillery 扩展体系的最佳入门样本。

二、插件的两种形态:v1 构造函数与 v2{ Plugin }导出

从 Artillery 的插件加载源码 packages/artillery/lib/load-plugins.ts 可以看到,插件包在加载时会按导出形状被识别为不同版本:

  • v1 插件:模块直接导出一个构造函数(typeof PluginExport === 'function'),实例化时接收script.config和事件总线;
  • v2 插件:模块导出一个对象,其中含Plugin构造函数(typeof PluginExport.Plugin === 'function'),实例化时接收完整的script、事件总线与运行选项。

本示例采用 v2 形态,在 index.js 中通过一行代码完成导出:

module.exports.Plugin = ArtilleryHelloWorldPlugin;

在本地运行器 packages/artillery/lib/platform/local/worker.ts 中,两种形态的实例化方式对应为:

// v1:传 script.config result.plugin = new result.PluginExport(script.config, stubEE); // v2:传整个 script result.plugin = new result.PluginExport.Plugin(script, stubEE, options);

从源码结构看,v2 之所以能拿到整个script,是为了让插件有更大的自由度去修改脚本对象(例如注入处理器函数),而 v1 只拿到配置部分。本示例使用的 v2 接口正是当前主推的形态。

三、插件包命名与加载路径约定

Artillery 默认会到 Node.js 的包查找路径中寻找插件包,且要求包名带artillery-plugin-前缀——因此hello-world插件对应的包名必须是artillery-plugin-hello-world,这一点同时体现在 package.json 的name字段与加载逻辑中。

加载顺序在 load-plugins.ts 中定义得非常明确:

  1. 先按裸包名(bare specifier)从标准node_modules解析(用户安装的副本优先);
  2. 其次查找 Artillery 内置包目录;
  3. 最后才尝试ARTILLERY_PLUGIN_PATH环境变量中冒号(:)分隔的各个路径。
let requirePaths = ['', BUILTIN_PACKAGES_DIR]; if (process.env.ARTILLERY_PLUGIN_PATH) { requirePaths = requirePaths.concat(process.env.ARTILLERY_PLUGIN_PATH.split(':')); }

因此,当你在开发插件、尚未把包发布安装到node_modules时,用ARTILLERY_PLUGIN_PATH指向上级目录即可让 Artillery 找到它。原文档在 README 中也明确提示了这一点:ARTILLERY_PLUGIN_PATH正是为“开发中的插件”提供的额外查找位置。

除路径外,还可以通过环境变量ARTILLERY_PLUGINS(JSON 字符串)附加额外的插件配置,详见 load-plugins.ts 中的loadPluginsConfig实现。

四、剖析插件实现:构造函数里做了什么

打开 index.js,整个插件只有三部分:构造函数、一个处理器函数、一个cleanup钩子。下面逐段拆解。

4.1 保存 script 与 events

function ArtilleryHelloWorldPlugin(script, events) { this.script = script; this.events = events; // ... }

构造函数接收两个参数:

  • script:完整的测试脚本对象(即 YAML 解析后的config+scenarios结构);
  • events:一个 EventEmitter,插件可以订阅stats(新一批指标产生时触发)、done(所有虚拟用户完成时触发)事件,也可以用它发出自定义指标。

从 worker.ts 的调用来看,v2 插件实例化时传入的正是完整script、事件桩(stubEE)与options。注释中还特别说明:v1/v2 插件不会从每个 runner 实例直接订阅stats/done,而是在launch-platform层统一订阅聚合后的结果,避免插件收到未聚合的零散事件。

4.2 读取插件自身的配置

const pluginConfig = script.config.plugins['hello-world']; this.greeting = pluginConfig.greeting || 'hello, world';

插件可以直接访问script.config.plugins['hello-world']拿到自己在测试脚本中的配置块,并给出默认值兜底。这正是原文档所说的“如何检查测试脚本属性”——配置、target、phases 等一切脚本内容对插件都是可见的,例如:

debug('target is:', script.config.target);

4.3 向所有场景注入 beforeRequest 钩子

script.config.processor = script.config.processor || {}; script.config.processor.pluginHelloWorldBeforeRequestHook = (_req, _vuContext, events, next) => { console.log(this.greeting); events.emit('counter', 'greeting_count', 1); return next(); }; script.scenarios.forEach((scenario) => { scenario.beforeRequest = scenario.beforeRequest || []; scenario.beforeRequest.push('pluginHelloWorldBeforeRequestHook'); });

这段代码做了三件事,正是插件“动手术”的典型手法:

  1. 在script.config.processor对象中注册一个自定义函数(Artillery 的处理器机制);
  2. 该函数的签名符合beforeRequest钩子约定:接收请求对象_req、虚拟用户上下文_vuContext、events事件总线以及next回调;调用next()表示钩子结束、继续后续流程;
  3. 遍历script.scenarios,把钩子名追加到每个场景的beforeRequest列表末尾——由此实现对所有场景生效,无需用户在 YAML 里逐个声明。

HTTP 引擎在 packages/artillery/lib/core/engine_http.ts 中会在每次请求发出前依次执行这些beforeRequest处理器(包括场景级与请求级),这也是“在请求前打印问候语”之所以能落地的底层机制。

4.4 上报自定义计数器

钩子内部通过events.emit('counter', 'greeting_count', 1)上报一个名为greeting_count的自定义计数器。控制台报告器 packages/artillery/lib/console-reporter.ts 会读取report.counters并按名称排序打印,最终在测试报告尾部以greeting_count: N的形式呈现,无需任何额外配置即可看到插件产生的新指标。

4.5 清理钩子 cleanup

ArtilleryHelloWorldPlugin.prototype.cleanup = (done) => { debug('cleaning up'); done(null); };

Artillery 在退出前会调用每个插件的cleanup,给插件一次冲刷在途数据、写盘等收尾机会。在 packages/artillery/lib/launch-platform.ts 与 packages/artillery/lib/platform/local/worker.ts 中都有对plugin.cleanup的调用,且在done回调中传入错误对象(null表示无错)。本示例只打印一条调试日志,实际插件常在此处刷新缓存、关闭连接或写报告文件。

五、配套的测试脚本与运行方式

示例自带的 test.yml 是验证插件的完整测试脚本:

config: target: "http://asciiart.artillery.io:8080" phases: - arrivalRate: 1 duration: 10 plugins: hello-world: greeting: "Hello world! 👋" scenarios: - flow: - get: url: "/"

要点解读:

  • config.plugins.hello-world.greeting是插件的配置块,会被插件构造函数读取并覆盖默认问候语;
  • phases以每秒 1 个新虚拟用户的速率持续 10 秒;
  • 场景只有一个 HTTPGET /请求,用于触发beforeRequest钩子。

在示例目录下运行(注意:需先安装 Artillery 本体,示例插件作为本地包由ARTILLERY_PLUGIN_PATH指向):

ARTILLERY_PLUGIN_PATH=`pwd`/.. DEBUG=plugin:hello-world artillery run test.yml

命令说明:

  • ARTILLERY_PLUGIN_PATH=pwd/..:把插件包所在目录(示例的上级目录,即examples/,其中包含名为artillery-plugin-hello-world的包目录)加入插件查找路径;
  • DEBUG=plugin:hello-world:开启debug库中该插件命名空间的调试输出,对应 index.js 中的require('debug')('plugin:hello-world');
  • artillery run test.yml:以本地模式运行测试。

运行后,终端会依次出现插件初始化时的调试信息(如target is:与cleaning up)、每条请求前的问候语打印,以及报告尾部由插件上报的greeting_count计数器(见上文两张截图)。loadPlugins的单元测试 packages/artillery/test/unit/load-plugins.test.js 与 CLI 测试 packages/artillery/test/cli/custom-plugin.test.js 均演示了通过ARTILLERY_PLUGIN_PATH加载本地插件这一方式的自动化验证。

六、插件的加载与初始化调用链

把上面的零散证据串起来,一个插件从被识别到生效的完整链路是:

  1. 解析插件规格:loadPluginsConfig合并脚本内的config.plugins与环境变量ARTILLERY_PLUGINS(load-plugins.ts);
  2. 按序查找包:依次在裸包路径、内置包目录、ARTILLERY_PLUGIN_PATH中require.resolve,再通过import()加载(兼容 CJS 与 ESM,见 load-plugins.ts);
  3. 识别版本:按导出形状判定 v1/v2(load-plugins.ts);
  4. 实例化:在本地 worker 中按版本调用构造函数(worker.ts),在平台层(如 Fargate/Lambda 场景)则先给一个深拷贝的dummyScript做事件订阅注册,避免在分发阶段就修改真实脚本对象(launch-platform.ts);
  5. 钩子生效:插件修改script.config.processor与各scenario.beforeRequest,HTTP 引擎在每次请求前执行这些处理器(engine_http.ts);
  6. 指标呈现:插件通过events.emit('counter', ...)上报的计数器由控制台报告器汇总打印(console-reporter.ts);
  7. 退出清理:cleanup被依次调用(launch-platform.ts)。

值得一提的是,平台层在初始化时给 v1/v2 插件传递的是深拷贝的 dummyScript,源码注释明确指出:如果让插件直接操作真实脚本对象并在其中附加处理器,会导致派发 worker 时失败(launch-platform.ts)——这是插件开发中容易踩坑、但示例这种“单机运行”场景不会触发的细节。

七、在此基础上还能做什么

原文档在 README 的 “Learn more” 一节推荐了更多成熟插件作为参考,这些参考实现大多已在当前仓库中开源,可以直接对照阅读:

  • packages/artillery-plugin-publish-metrics:向 Datadog、Prometheus、CloudWatch、New Relic、Splunk、Mixpanel 等后端发布指标,展示了订阅stats事件做指标转发的能力;
  • packages/artillery-plugin-expect:在测试中对响应做断言,展示了在请求层面深度拦截与校验的模式;
  • packages/artillery-plugin-apdex:基于响应时间计算 Apdex 评分,展示了利用聚合指标派生新指标的做法;
  • packages/artillery-plugin-metrics-by-endpoint:按端点拆分指标,展示了结合 URL 模板场景的处理方式;
  • packages/artillery-plugin-fake-data:生成随机测试数据,展示了在脚本中注入数据生成能力;
  • packages/artillery-plugin-slack:测试结束后推送通知,展示了订阅done事件的典型用法。

此外,Artillery 的官方扩展 API 文档与《创建自定义插件》博客是深入了解插件机制的权威补充资料(原文已给出链接,此处不再罗列外部地址)。核心要点可以归纳为:插件接口 + Node.js 生态(“任何需求几乎都有对应的 npm 包”)是 Artillery 扩展能力的双引擎——你的插件完全可以调用任意 Node.js 模块来完成指标入库、消息通知、数据加工等任何定制逻辑。

八、小结

通过artillery-plugin-hello-world这个最小示例,我们完整走通了 Artillery 插件开发的全部关键环节:

能力示例中的落点源码依据
v2 插件导出module.exports.Pluginindex.js、worker.ts
读取脚本配置script.config.plugins['hello-world']index.js
注入全局钩子改写scenario.beforeRequestindex.js、engine_http.ts
自定义指标events.emit('counter', ...)index.js、console-reporter.ts
退出清理prototype.cleanupindex.js、launch-platform.ts
开发期加载ARTILLERY_PLUGIN_PATHload-plugins.ts

对任何希望扩展 Artillery 的团队或个人而言,这个示例提供了可复制的最小骨架:定义{ Plugin }导出 → 读取配置 → 注入处理器 → 上报指标 → 实现清理。后续只需按需求替换业务逻辑,就能把它升级为功能完备的生产级插件。

  • 性能测试
  • 接口测试
  • 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
点击查看免费下载

相关推荐

上一篇:SymPy 特殊函数专题指南:从 Dirac Delta 到超几何函数的内置特殊函数库
下一篇:Rails Girls Guides终极翻译指南:搭建跨文化交流的编程教育桥梁

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 2:50:16

KatelyaTV配置文件详解:从基础到增强版94个片源配置指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 2:48:35

OpenCV 4.8.0与MinGW编译实战:从CMake配置到Qt集成完全指南

简介:从源代码构建OpenCV是许多Windows开发者避开ABI兼容陷阱的通用思路。MSVC与MinGW采用不同的C运行时和链接库格式,官方预编译包无法直接在GCC工具链下使用。通过CMake生成MinGW Makefiles工程,可以控制模块选择、关闭非必要加速项&#x…

作者头像 李华
网站建设 2026/9/25 2:46:33

WinForm+SQLServer外卖系统开发:建模、事务、并发与避坑指南

简介:这套基于WinForm与SQL Server的外卖系统项目,适合C#桌面开发学习者、课程设计或毕业设计参考。项目分为用户端、商家端、骑手端与管理端四个角色,覆盖商品浏览、跨店铺购物车、订单结算、钱包管理、商家接单、骑手派单及人员管理等典型业…

作者头像 李华