OpenClaw 测试性能
以证据为先。目标是真正提升 pnpm test、插件套件和插件检查器的速度/RSS,并保持覆盖率不变,而不是靠猜测来调整运行器。
工作流程
- 在编辑前阅读相关的本地
AGENTS.md文件:src/agents/AGENTS.md了解代理/导入热点。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了解出站/媒体/操作测试。
- 在修改代码前建立基线:
- 首选
pnpm test:perf:groups --full-suite --allow-failures --output <file>进行完整套件排名。 - 对于捆绑插件的广度,在跳转到完整的扩展扫描之前,先运行最小的相关
pnpm test:extensions:batch <plugin[,plugin...]>或插件检查器命令。 - 对于有范围的性能热点,使用:
/usr/bin/time -l pnpm test <file-or-files> --maxWorkers=1 --reporter=verbose - 用于怀疑导入开销大的情况,添加:
OPENCLAW_VITEST_IMPORT_DURATIONS=1 OPENCLAW_VITEST_PRINT_IMPORT_BREAKDOWN=1。
- 首选
- 将 wall/runner 噪声与真实文件开销分离:
- 比较 Vitest 持续时间、测试主体计时、导入分解、真实时间和最大 RSS。
- 当分组/完整套件的数字看起来不新鲜或有噪声时,重新运行单个文件。
- 如果完整套件的分组运行报告了一个通道失败,但 JSON 显示测试通过,则将其捕获为测试框架/噪声,并直接验证可疑文件。
- 根据收益和风险选择下一个攻击点:
- 高收益:一个文件/测试占据了数秒或 RSS 的大部分,并且有明显的根因。
- 高杠杆:一个插件或 SDK 桶导致每次插件检查器或扩展批次运行都加载了庞大的运行时。
- 低风险:静态描述符、目标解析、路由、认证绕过、设置提示、注册表固件或测试服务器生命周期。
- 高风险:真实的内存/运行时行为、实时提供者、协议合约或广泛的生成环境重构。
- 修复根本原因,而非症状:
- 将静态元数据/解析移入窄小的助手或轻量级工件中,这些工件被完整运行时和快速路径重用。
- 优先使用依赖注入、仅加载插件查找、显式固件和纯助手,而不是广泛的 mock。
- 当新握手无关紧要时,重用套件级别的服务器/客户端。
- 除非测试验证调度,否则保持调度器/后台循环关闭。
- 在插件路径中,将静态元数据移入清单/轻量级工件,并将运行时插件加载保持在显式的执行边界之后。
- 保持覆盖形状:
- 除非确切的生成环境组合被提取到一个命名的助手中并经过测试,否则不要删除一个缓慢的集成验证。
- 当跨组件连接重要时,保留一个廉价的集成冒烟测试。
- 明确说明删除了哪些偶然的覆盖(如果有的话)。
- 在更改后,使用相同的命令重新进行基准测试,并计算秒数和收益百分比。
- 当被要求时,或者当此线程正在跟踪一个报告时,更新运行报告。包括前后命令、工件、覆盖说明、验证和下一个攻击顺序。
- 暂存目标路径,使用标准 Git 提交,并在用户要求提交/推送时推送。只暂存为此攻击修改的文件。
插件套件工作流程
当性能工作涉及捆绑插件、插件检查器、SDK 桶、包边界测试或扩展套件时,使用此部分。
- 首先映射套件形状:
- 源测试:
pnpm test extensions/<id>或pnpm test:extensions:batch <id> - 包边界:
pnpm run test:extensions:package-boundary:canary和pnpm run test:extensions:package-boundary:compile - 所有捆绑源测试:
pnpm test:extensions - 插件导入内存:
pnpm test:extensions:memory -- --json .artifacts/test-perf/extensions-memory.json - 插件检查器/报告工作:将报告原语保留在
plugin-inspector中;保持包装器精简,并在命令支持时收集峰值 RSS。
- 源测试:
- 从窄小开始,然后扩大:
- 一个插件更改:运行该插件的测试和插件检查器切片。
- SDK/公开桶更改:添加代表性的提供者、通道、内存和功能插件。
- 加载器/运行时镜像更改:根据需要添加包边界检查和构建/打包验证。
- 未知的共享插件行为:在运行
pnpm test:extensions之前,先运行test:extensions:batch组。
- 将插件检查器失败视为产品信号:
- JSON 必须可解析。
- 警告/错误必须分类,而不是隐藏。
- 运行时捕获应安静且兼容配置。
- 命令输出应包含真实时间、退出代码和峰值 RSS(如果可用)。
- 遵循
$openclaw-testing进行主机选择。可信的源基准测试可以在本地运行,前提是机器/负载条件相当。当清洁打包、Linux/平台行为、隔离或显式远程请求是验证的一部分时,使用$crabbox;仅重用和清理拥有的租约。 - 如果插件性能对包产物敏感,则切换到
release-openclaw-plugin-testing和包验收,而不是仅信任源级别的计时。
指标收集
在前后至少收集一个稳定的指标。优先使用相同的机器和相同的命令。对于 Testbox 比较,尽可能使用相同的 tbx_... ID。
| 指标 | 用途 | 首选来源 |
|---|---|---|
| wall time | 用户可见的套件开销 | /usr/bin/time -l,测试包装器持续时间,Testbox 运行时间 |
| Vitest 持续时间 | 测试主体/导入开销 | 每个文件/分片的 Vitest 输出 |
| 导入持续时间 | 广泛的桶/运行时加载 | OPENCLAW_VITEST_IMPORT_DURATIONS=1 |
| max RSS | 内存压力和 OOM 风险 | /usr/bin/time -l,pnpm test:extensions:memory,包装器内存摘要 |
| CPU/user/sys | CPU 密集型与等待型分割 | 本地使用 /usr/bin/time -l,当本地 CPU 噪声大时使用 Testbox 作业计时 |
| 堆证据 | 真正的泄漏与保留的模块图 | openclaw-test-heap-leaks 工作流程 |
带有 CPU/RSS 的本地限定命令:
timeout 240 /usr/bin/time -l pnpm test <file> --maxWorkers=1 --reporter=verbose
插件导入内存分析:
pnpm build
pnpm test:extensions:memory -- --top 20 --json .artifacts/test-perf/extensions-memory.json
目标插件导入内存:
pnpm test:extensions:memory -- --extension discord --extension telegram --skip-combined
堆/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>
当 RSS 在各个间隔内持续增长、工作进程 OOM 或可疑命令存在应用对象保留时,使用 openclaw-test-heap-leaks。在快照或保留器支持之前,不要将 RSS 增长称为泄漏。
常见根因
- 为静态数据加载了完整的捆绑通道/插件运行时。
- 当已加载的固件或纯解析器就足矣时,使用了
getChannelPlugin()回退。 - 将广泛的
api.ts、runtime-api.ts、test-api.ts或插件 SDK 桶拉入了热测试路径。 - SDK 根别名或包桶将聚焦的子路径拉回了广泛的插件图中。
- 插件检查器加载运行时代码仅仅是为了渲染元数据、报告或 CI 策略分数。
- 捆绑插件捕获重用了真实配置/主目录状态,而不是合成的、编校的、隔离的状态。
- 部分真实的 mock 在广泛的模块周围使用了
importActual()。 - 在每个测试循环中使用
vi.resetModules()加上全新的导入。 - 在
beforeAll中播种测试插件注册表,而在afterEach中重置运行时状态。 - 当状态重置就足够时,在每个测试中进行网关/服务器/客户端启动。
- 运行时/默认模型/认证选择由空闲快照或固件支付。
- 在检查参数是否包含插件拥有的字段之前,触发了插件拥有的媒体/操作发现。
- 并行的 Vitest 运行共享了
node_modules/.experimental-vitest-cache,但没有设置不同的OPENCLAW_VITEST_FS_MODULE_CACHE_PATH值。
基准测试命令
限定范围的文件:
timeout 240 /usr/bin/time -l pnpm test <file> --maxWorkers=1 --reporter=verbose
带有导入分解的限定范围文件:
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
验证
- 始终运行能证明变更的目标测试表面。
- 源代码更改:在推送前进行聚焦的本地测试 + 目标类型检查/lint/格式检查;
pnpm check:changed可选。PR CI = 广泛验证。Testbox 仅用于 CI/本地差距、即时授予;选择时重用预热的 Testbox。 - 对于仅测试更改,运行
pnpm test:changed或确切的编辑测试。 - 修改懒加载、捆绑产物、包边界、动态导入、构建输出或公开面时,运行
pnpm build。 - 对于插件 SDK/桶/运行时更改,当公共 API 面可能漂移时,使用
pnpm plugin-sdk:api:diff -- --base <base-sha> --head <head-sha>比较确切的提交。对于 PR 本地验证,使用分支合并基础作为<base-sha>,使用确切的经过测试的头提交作为<head-sha>。 - 对于插件套件性能修复,至少验证一个代表性的插件批次加上更改的门控;如果错误只存在于打包的产物中,则使用包验收。
- 如果依赖项丢失/过时,运行
pnpm install并重试完全失败的命令一次。 - 使用报告格式:
| 指标 | 之前 | 之后 | 收益 |
| ---------------- | ---: | ---: | -------------: |
| 文件 wall 时间 | `Xs` | `Ys` | `-Zs` (`P%`) |
| 最大 RSS | `XMB` | `YMB` | `-ZMB` (`P%`) |
| CPU user/sys | `X/Ys` | `A/Bs` | 解释 |
交接
保持最终输出简洁:
- 根本原因。
- 套件/插件范围。
- 更改的文件。
- 前后 wall 时间、Vitest/导入时间、CPU 和 RSS 数值(如果可用)。
- 泄漏分类(如果涉及内存):真正泄漏、保留的模块图或不确定。
- 覆盖保持情况。
- 验证命令。
- 远程验证的 Testbox ID 或工作流 URL。
- 提交哈希和推送状态。