OpenClaw 测试性能调优实战:基于 openclaw-test-performance 技能的基准、诊断与优化方法论
【免费下载链接】openclawThe AI that really does things. Any OS. Any Platform. The lobster way. 🦞项目地址: https://gitcode.com/GitHub_Trending/cl/openclaw
OpenClaw 是一个包含 1.9 万多个 TypeScript 源文件、100+ 扩展插件与数百个 Vitest 配置的大型 monorepo,其pnpm test全量套件与插件批跑的成本本身就是工程问题。本文基于仓库中的测试性能技能文档 .agents/skills/openclaw-test-performance/SKILL.md,完整继承其“先证据、后动手”的工作流、指标采集矩阵、插件套件专项流程与常见根因清单,并结合package.json脚本定义、test/vitest/vitest.performance-config.ts源码实现进行纵深解读。读完本文,你将掌握一套可复用的测试提速方法:如何建立可信基线、如何区分 runner 噪声与真实文件成本、如何定位 import 热点与内存增长、以及如何用 before/after 报告证明优化收益。
核心原则:证据优先,拒绝猜测式调参
该技能开篇就定下总纲(原文即 “Use evidence first”):目标是让真实的pnpm test、plugin-suite 与 plugin-inspector 在保持覆盖度不变的前提下获得速度/RSS 提升,而不是靠猜测去调整 runner 参数。这句话隐含三个可操作约束:
- 必须有 before/after 对照:任何优化都要在同一台机器、同一条命令下采集至少一个稳定指标;
- 覆盖度形状不能塌:不能靠删测试、砍断言来“提速”;
- 修根因,不修症状:优化动作应落在导入图、生命周期、fixture 设计上。
从源码结构看,这套方法在仓库里有真实的执行基座:根 vitest.config.ts 仅一行,把入口转发到 test/vitest/vitest.config.ts,而 test/vitest/ 目录下有上百个按子系统切分的配置文件(vitest.unit-fast.config.ts、vitest.full-core-*.config.ts、vitest.extension-*.config.ts等),说明 OpenClaw 的测试矩阵本身就是“分片治理”的对象,性能工作流因此强调“先映射套件形状,再动手”。
标准工作流:九步法逐条解读
技能文档的 Workflow 章节给出了完整的九步流程,这里是它的完整继承与逐条扩充。
第 1 步:改代码前,先读本地 AGENTS.md
技能要求在做性能改动前读取相关子系统的 AGENTS.md 约定文件,因为每个子系统有自己的导入热点与懒加载纪律:
| 文档 | 关注点 |
|---|---|
| src/agents/AGENTS.md | agent 层导入热点 |
| src/channels/AGENTS.md、src/plugins/AGENTS.md | 插件/通道的懒加载纪律 |
| src/gateway/AGENTS.md | 服务生命周期测试 |
test/helpers/AGENTS.md、src/channels/plugins/contracts/test-helpers/AGENTS.md | 共享契约测试辅助工具 |
| src/infra/outbound/AGENTS.md | outbound/媒体/动作类测试 |
这些文件均已确认存在于仓库中。其用意是:性能优化的“攻击面”往往受子系统约定约束(例如某目录禁止在beforeAll里播种插件注册表),不读约定就可能写出违反架构纪律的“优化”。
第 2 步:改动前建立基线
基线采集按场景分档:
- 全量排名:优先
pnpm test:perf:groups --full-suite --allow-failures --output <file>,对全量套件做分组排名; - 插件广度:先跑最小相关的
pnpm test:extensions:batch <plugin[,plugin...]>或 plugin-inspector 命令,不要一上来就全量扫扩展; - 范围热点:
/usr/bin/time -l pnpm test <file-or-files> --maxWorkers=1 --reporter=verbose; - 疑似 import 重:叠加环境变量
OPENCLAW_VITEST_IMPORT_DURATIONS=1 OPENCLAW_VITEST_PRINT_IMPORT_BREAKDOWN=1。
这些命令在 package.json 中均有真实脚本定义(行号见下):test:perf:groups映射到 scripts/test-group-report.mts(package.json L2072),test:perf:profile:runner映射到 scripts/run-vitest-profile.mts(L2078),test:extensions/test:extensions:batch/test:extensions:memory分别映射到 scripts/test-projects.mts、scripts/test-extension-batch.mts、scripts/profile-extension-memory.mts(L2031–L2033)。也就是说,技能文档里的每一条命令都不是口头约定,而是仓库脚本的精确投影。
第 3 步:分离 wall 噪声与真实文件成本
这是工作流中最反直觉、也最容易被忽视的一步。技能要求交叉对比四种数据:Vitest duration、test body timing、import breakdown、wall time 与 max RSS。具体做法:
- 分组/全量数字看起来陈旧或噪声大时,单文件重跑验证;
- 如果全量分组跑报告了某个 lane 失败,但 JSON 显示测试其实通过了,要把它记录为harness/噪声,并直接验证可疑文件,而不是顺着假失败去改代码。
这一步的本质是:/usr/bin/time -l测到的 wall time 包含调度、进程启动、磁盘缓存等 runner 成本,只有把它和 Vitest 自身的 per-file duration 对账,才能知道“慢”发生在测试本身还是运行环境。
第 4 步:按收益与风险挑选下一个攻击目标
技能给出了明确的优先级矩阵:
- 高收益:单个文件/测试独占数秒或大量 RSS,且根因清晰;
- 高杠杆:一个插件或 SDK barrel 导致每次 plugin-inspector / extension-batch 运行都加载庞大 runtime;
- 低风险:静态描述符、目标解析、路由、auth bypass、setup 提示、registry fixture、测试 server 生命周期;
- 高风险:真实内存/runtime 行为、live provider、协议契约、大范围生产重构。
第 5 步:修根因,不修症状
技能列出的修复策略(完整继承):
- 把静态元数据/解析移入窄辅助函数或轻量工件,供完整 runtime 与快速路径复用;
- 优先依赖注入、仅已加载插件的查找(loaded-plugin-only lookup)、显式 fixture、纯辅助函数,而非宽泛 mock;
- 当新握手无关紧要时,复用 suite 级 server/client;
- 调度器/后台循环默认关闭,除非测试本身在证明调度行为;
- 插件路径上,把静态元数据放入 manifest/轻量工件,运行时插件加载留在显式执行边界之后。
第 6 步:保护覆盖度形状
三条硬规则:除非把精确的生产组合抽成命名辅助函数并单独测试,否则不得删除慢速集成证明;跨组件接线重要时保留一个廉价集成 smoke;如果确实移除了顺带覆盖,要明确说明移除了什么。
第 7–9 步:重测、写报告、按最小范围提交
- 改动后用同一条命令重新基准测试,计算秒数与百分比收益;
- 在被要求或本线程在追踪报告时,更新运行报告:包含 before/after 命令、工件、覆盖度说明、验证方式、下一个攻击顺序;
- 只 stage 本次攻击触碰的文件,用标准 Git 提交,仅在用户要求时推送。
指标采集:六维指标矩阵与命令
技能要求“before/after 至少采集一个稳定指标,优先同一台机器、同一条命令;Testbox 对比时尽量用同一个tbx_...id”。以下是原文的完整指标矩阵:
| 指标 | 用途 | 首选来源 |
|---|---|---|
| wall time | 用户可见的套件成本 | /usr/bin/time -l、测试包装器 duration、Testbox 运行时长 |
| Vitest duration | 测试体/import 成本 | Vitest 按文件/分片输出 |
| import duration | 宽 barrel/runtime 加载 | OPENCLAW_VITEST_IMPORT_DURATIONS=1 |
| max RSS | 内存压力与 OOM 风险 | /usr/bin/time -l、pnpm test:extensions:memory、包装器内存摘要 |
| CPU/user/sys | CPU 密集 vs 等待密集拆分 | 本地/usr/bin/time -l,本地 CPU 噪声大时用 Testbox job timing |
| heap evidence | 真泄漏 vs 保留的模块图 | openclaw-test-heap-leaks工作流 |
配套的标准采集命令(原文完整继承):
本地范围命令(带 CPU/RSS):
timeout 240 /usr/bin/time -l pnpm test <file> --maxWorkers=1 --reporter=verbose插件 import 内存画像:
pnpm build pnpm test:extensions:memory -- --top 20 --json .artifacts/test-perf/extensions-memory.json定向插件 import 内存:
pnpm test:extensions:memory -- --extension discord --extension telegram --skip-combinedHeap/RSS 升级路径:
pnpm test:perf:groups \ --config test/vitest/vitest.unit-fast.config.ts \ --allow-failures \ --output .artifacts/test-perf/unit-fast-memory.json pnpm test:perf:profile:runner -- \ --output-dir .artifacts/test-perf/vitest-runner-profile -- <file>其中 test/vitest/vitest.unit-fast.config.ts 是真实存在的单测快速档配置。技能还特别警告:在快照或 retainer 证据支持之前,不要把 RSS 增长称为“泄漏”——这一条与姊妹技能 .agents/skills/openclaw-test-heap-leaks/SKILL.md 的立场完全一致(“Treat snapshot-name deltas as triage evidence, not proof”)。
源码印证:import 计时环境变量如何实现
OPENCLAW_VITEST_IMPORT_DURATIONS并非文档里的虚构开关,其实现位于 test/vitest/vitest.performance-config.ts:loadVitestPerformanceConfig()把环境变量规范化为 Vitest 配置——OPENCLAW_VITEST_IMPORT_DURATIONS=1/true映射到experimental.importDurations = { print: true }(L59–L61),OPENCLAW_VITEST_PRINT_IMPORT_BREAKDOWN映射到experimental.printImportBreakdown = true(L62–L64)。该模块还有两个与技能文档强相关的细节:
- fs module cache 的目录所有权:
resolveVitestFsModuleCacheRoot()(L34–L36)把缓存根固定在当前 checkout的.cache/vitest下,源码注释明确解释了原因——“Linked worktrees may share node_modules; invalidating that cache would remove another checkout's transforms”(关联 worktree 可能共享 node_modules,失效该缓存会抹掉另一个 checkout 的转换结果)。这直接呼应技能“常见根因”里的一条:并行 Vitest 运行共享node_modules/.experimental-vitest-cache而未设置互异的OPENCLAW_VITEST_FS_MODULE_CACHE_PATH。代码中OPENCLAW_VITEST_FS_MODULE_CACHE_PATH优先、缺省回落到.cache/vitest/default(L52–L58),注释还解释了为什么默认叶目录不能放调度器的并发缓存叶——Vitest 在 lockfile 变化时会递归删除所选目录。 - 平台容忍:
isWindowsEnv()(L15–L21)同时检查process.platform === "win32"与RUNNER_OS=windows,说明这些开关在 CI 平台切换时行为可控。
该实现的测试覆盖在 test/vitest-performance-config.test.ts,可作为行为契约的进一步佐证。
插件套件专项工作流
当性能工作涉及 bundled 插件、plugin-inspector、SDK barrel、package-boundary 测试或扩展套件时,技能切换到专门的 Plugin-Suite Workflow,共五步,完整继承如下。
1. 先映射套件形状
- 源码测试:
pnpm test extensions/<id>或pnpm test:extensions:batch <id>; - 包边界:
pnpm run test:extensions:package-boundary:canary与pnpm run test:extensions:package-boundary:compile; - 全部 bundled 源码测试:
pnpm test:extensions; - 插件 import 内存:
pnpm test:extensions:memory -- --json .artifacts/test-perf/extensions-memory.json; - plugin-inspector/report 工作:报告原语留在
plugin-inspector内,wrapper 保持薄,命令支持时采集 peak RSS。
前两条包边界命令在 package.json L2034–L2036 中定义,均映射到 scripts/check-extension-package-tsc-boundary.mts,仅以--mode=canary/--mode=compile区分两种检查强度。
2. 先窄后宽
- 只改了一个插件:跑该插件的测试与 plugin-inspector 切片;
- 改了 SDK/公共 barrel:补上代表性的 provider、channel、memory、feature 类插件;
- 改了 loader/runtime mirror:按需补 package-boundary 检查与 build/package 证明;
- 共享插件行为不明:先跑
test:extensions:batch分组,再考虑pnpm test:extensions全量。
3. 把 plugin-inspector 失败当产品信号
JSON 必须可解析;warnings/errors 必须被分类而不是被隐藏;runtime capture 应当安静且对配置容忍;命令输出在可用时包含 wall time、exit code 与 peak RSS。
4. 主机选择遵循 openclaw-testing 技能
可信源码基准可在本地以可比机器/负载条件运行;当证明需要干净打包、Linux/平台行为、隔离或明确远程请求时,使用$crabbox,且只复用并清理自己拥有的 lease。
5. 打包工件敏感时切换验收口径
如果插件性能对 package-artifact 敏感,切换到release-openclaw-plugin-testing与 Package Acceptance,而不是相信纯源码计时。
常见根因清单(14 条,完整继承)
技能沉淀的“Common Root Causes”是该文档最有实战价值的一部分,逐条继承如下:
- 为拿静态数据加载了完整的 bundled channel/plugin runtime;
- 在已有已加载 fixture 或纯解析器可用的场景误用
getChannelPlugin()fallback; - 宽泛的
api.ts、runtime-api.ts、test-api.ts或 plugin-sdk barrel 被拉入热点测试; - SDK 根别名或包 barrel 把聚焦的子路径又拖回宽插件图;
- plugin-inspector 仅为渲染元数据、报告或 CI 策略评分而加载 runtime 代码;
- bundled 插件 capture 复用真实 config/home 状态,而非合成、脱敏、隔离状态;
- 围绕宽模块使用
importActual()的部分真 mock; vi.resetModules()加 per-test 循环内的 fresh import;- 测试插件注册表在
beforeAll播种,而 runtime 状态在afterEach重置(时序错配); - 状态重置本可解决问题,却 per-test 启动 gateway/server/client;
- 空闲快照或 fixture 也支付了 runtime/默认模型/auth 选择的成本;
- 未先检查参数是否包含插件专属字段,就触发了插件自有的 media/action discovery;
- 并行 Vitest 运行共享
node_modules/.experimental-vitest-cache,未设置互异的OPENCLAW_VITEST_FS_MODULE_CACHE_PATH(源码印证见上文 test/vitest/vitest.performance-config.ts); - 以上任何一条的组合——文档将其作为持续更新的“模式库”使用。
基准命令全集
技能“Benchmark Commands”章节的完整命令表(可直接复制):
范围文件:
timeout 240 /usr/bin/time -l pnpm test <file> --maxWorkers=1 --reporter=verbose范围文件 + import 分解:
timeout 240 /usr/bin/time -l env \ OPENCLAW_VITEST_IMPORT_DURATIONS=1 \ OPENCLAW_VITEST_PRINT_IMPORT_BREAKDOWN=1 \ pnpm test <file> --maxWorkers=1 --reporter=verbose分组套件:
pnpm test:perf:groups --full-suite --allow-failures \ --output .artifacts/test-perf/<name>.json扩展批跑:
pnpm test:extensions:batch <plugin[,plugin...]> -- --reporter=verbose全部扩展测试:
pnpm test:extensions包边界检查:
pnpm run test:extensions:package-boundary:canary pnpm run test:extensions:package-boundary:compile复用已有的 Vitest JSON 报告:
pnpm test:perf:groups --report <vitest-json> \ --output .artifacts/test-perf/<name>.json验证清单与报告格式
改完之后,技能要求按以下清单验证(完整继承):
- 始终跑能证明该改动的那个目标测试面;
- 源码改动在推送前跑
pnpm check:changed(package.json L1651 映射到 scripts/check-changed.mjs);maintainer Testbox 模式下在已加热的 Testbox 中跑; - 仅测试改动时跑
pnpm test:changed(L1923,映射到 scripts/test-projects.mts 的--changed origin/main)或精确到被编辑的测试; - 触碰懒加载、bundled 工件、包边界、动态导入、构建输出或公共面时跑
pnpm build; - 插件 SDK/barrel/runtime 改动可能导致公共 API 面漂移时,用
pnpm plugin-sdk:api:diff -- --base <base-sha> --head <head-sha>对比精确提交(L1840,映射到 scripts/plugin-sdk-api-diff.mts);PR 本地证明时,<base-sha>用分支 merge base,<head-sha>用实际测试的头提交; - 插件套件性能修复至少验证一个代表性插件批次 + 变更的门禁;bug 只存在于打包工件中时改用 Package Acceptance;
- 依赖缺失/陈旧时,跑一次
pnpm install并重试原失败命令一次。
收益报告采用固定表格格式:
| Metric | Before | After | Gain | | -------------- | -----: | -----: | ------------: | | File wall time | `Xs` | `Ys` | `-Zs` (`P%`) | | Max RSS | `XMB` | `YMB` | `-ZMB` (`P%`) | | CPU user/sys | `X/Ys` | `A/Bs` | explain |交付清单(Handoff)
技能要求最终交付保持简洁,逐项包含:根因;套件/插件范围;变更文件;可用的 before/after wall、Vitest/import、CPU、RSS 数字;涉及内存时的泄漏分类(真泄漏、保留的模块图、或证据不足);保留的覆盖度;验证命令;远程证明的 Testbox ID 或 workflow URL;commit hash 与推送状态。
小结:一套可迁移的测试性能方法论
回看这份技能文档,它的价值不在单条命令,而在把“测试性能”拆成了可审计的闭环:读约定 → 建基线 → 对账噪声 → 按收益/风险选点 → 修根因 → 保覆盖 → 同命令复测 → 结构化报告 → 最小范围提交。仓库中的实现与之一一对应:scripts/test-group-report.mts、scripts/run-vitest-profile.mts、scripts/profile-extension-memory.mts、scripts/check-extension-package-tsc-boundary.mts等脚本是命令层的执行体,test/vitest/vitest.performance-config.ts 是 import 计时与 fs 缓存开关的配置层,而姊妹技能 .agents/skills/openclaw-test-heap-leaks/SKILL.md 则承接了本文指标矩阵中“heap evidence”一格的深入调查——当 RSS 跨间隔持续增长、worker OOM 或可疑命令存在应用对象保留时,按 heap-leaks 技能采集快照并对比 delta,而不是停留在 RSS 曲线上猜测。对于任何拥有大体量测试矩阵的 TypeScript monorepo,这套“证据先于假设”的流程都具备直接迁移价值。
【免费下载链接】openclawThe AI that really does things. Any OS. Any Platform. The lobster way. 🦞项目地址: https://gitcode.com/GitHub_Trending/cl/openclaw
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考