opencode 手动性能测试套件:基于 Playwright 与 Chrome Trace 的 App 性能诊断实践
本文围绕 packages/app/e2e/performance 目录下的手动性能测试套件(manual performance suite)展开:它是 opencode 桌面应用(packages/app,Solid + Vite 构建的渲染进程)用来量化高负载交互性能的诊断体系,被刻意排除在常规本地与 CI 的 Playwright 发现流程之外。读完后你将掌握:如何构建并运行生产包下的基准场景、如何理解 benchmark fixture 的指标上报契约(BENCHMARK / BENCHMARK_PAGE JSON 行)、如何用 Chrome DevTools Trace 做渲染进程级剖析,以及套件遵循的"只断言场景完成与指标采集、不设定机器相关性能预算"的设计原则。
一、套件定位与运行机制
应用的高负载性能诊断集中在 packages/app/e2e/performance,与常规回归测试物理隔离。它被排除在正常本地和 CI 的 Playwright 发现之外,原因是基准测试运行时间长、且依赖生产构建,不适合混入快速反馈回路。
基准配置的核心行为定义在 playwright.config.ts:它继承主 Playwright 配置,将 testDir 锁定到 performance 目录本身(unit/** 被排除在 Playwright 发现外),并在 webServer 中强制使用生产构建:
// packages/app/e2e/performance/playwright.config.ts(节选)
webServer: {
...config.webServer,
command: `bun run build && bun run serve -- --host 0.0.0.0 --port ${port} --strictPort`,
reuseExistingServer: false,
}
即:先 bun run build 构建应用、再以 vite preview 托管生产包,然后串行执行场景(fullyParallel: false、workers: 1)。这与 AGENTS.md 中"对生产构建运行基准""串行运行基准以避免跨测试竞争"的原则一一对应。
1.1 运行方式
从 packages/app 目录显式运行:
bun run test:bench
在 packages/app/package.json 中,该脚本展开为两步:
"test:bench": "bun test ./e2e/performance/unit && playwright test --config e2e/performance/playwright.config.ts"
先跑 e2e/performance/unit 下的纯单元测试(指标计算、探针、trace 写出等模块的确定性验证),再跑 Playwright 浏览器基准。
Windows PowerShell 下需要显式设置单 worker:
$env:PLAYWRIGHT_WORKERS = "1"
bun run test:bench
此外还有一组配套的显式入口:
# 运行指定场景(可配合 trace 目录,见下文)
bunx playwright test --config e2e/performance/playwright.config.ts \
timeline/session-tab-switch-benchmark.spec.ts
1.2 场景覆盖范围
套件当前的基准场景覆盖五类高价值交互(可在各 spec 文件中逐一核对):
- 冷/热会话标签页切换计时:session-tab-switch-benchmark.spec.ts 对 cold/hot 各跑 5 轮(可用
SESSION_TAB_SWITCH_RUNS覆盖),v2 布局下再按 review 面板开/关分拆,统计firstDestinationObservedMs、firstCorrectObservedMs、stableObservedMs的 min/median/max; - 首页会话点击计时:home-tab-navigation-benchmark.spec.ts 将"打开首页会话并绘制标题栏 tab"拆分为 content 与 titlebar-tab paint 两段测量;
- 单会话标签关闭计时:关闭唯一会话 tab 后经由稳定的 home 恢复路径绘制首页;
- 缓存会话重绘与变更追踪:session-tab-flash.spec.ts 采样点击后缓存会话的重绘,并验证所有已打开会话 tab 的预取行为;
- 流式时间线吞吐诊断:session-timeline-benchmark.spec.ts 度量流式渲染的吞吐、RAF 回调间隔分布、长任务、几何稳定性与重挂载诊断,并按"新布局开关 × review 面板开/关 × 是否携带 diff"组合出多个场景。
已提交的 smoke 与 regression 测试则继续负责分页、tab 绘制、上下文调整、折叠状态、composer 间距等正确性覆盖——基准套件不重复承担正确性断言。
二、benchmark fixture:统一的上报契约
所有基准共享同一个 benchmark fixture,定义在 benchmark.ts。一个新基准应当"看起来像一个普通 Playwright 测试":
import { benchmark, expect } from "../benchmark"
benchmark("measures one interaction", async ({ page, report }) => {
// Only scenario-specific setup and interaction belong here.
report({ durationMs: 42 })
})
fixture 的行为约束(对照 benchmark.ts 源码):
- 强制
report():report只允许调用一次,重复调用直接抛错(Benchmark reported metrics more than once);benchmarkResult是auto: true的钩子,测试结束时若没有上报且测试状态与预期一致,会以Benchmark did not report metrics让该测试失败——这是"断言指标采集完成"而非断言数值的关键实现; - 自动命名与关闭 trace:通过覆写
pagefixture,为每个页面安装observePerformancePage诊断,结束时调用reportPerformancePage停止 trace 并输出诊断行; - 导航历史捕获与失败附载:
framenavigated事件记录主框架导航 URL 序列;当testInfo.status !== testInfo.expectedStatus时,把导航历史以performance-navigations附件挂到测试报告上,便于回放失败场景的页面路径; - 一致的指标输出:每个基准结束时输出一行
BENCHMARKJSON。
2.1 BENCHMARK / BENCHMARK_PAGE 两行诊断协议
源码 benchmark.ts#L29-L46 输出的 BENCHMARK 行比 README 简写版更完整:
BENCHMARK {"schemaVersion":2,"runID":"...","name":"...","status":"passed","expectedStatus":"expected","retry":0,
"repeatEachIndex":0,
"context":{"project":"chromium","platform":"darwin", ...场景 context},
"metrics":{...},
"error":undefined}
每个被观察的页面在最终带状态的 BENCHMARK 记录之前,先输出一条 BENCHMARK_PAGE(benchmark.ts#L120-L138),携带同一 runID、导航历史与可选的 trace 文件路径:
BENCHMARK_PAGE {"schemaVersion":2,"runID":"...","name":"...","context":{"platform":"...","trace":".../xxx.json",
"selectorTrace":false},
"navigations":["https://..."]}
runID 由 playwright.config.ts#L5 生成(ISO 时间戳 + 进程 PID 的 OPENCODE_PERFORMANCE_RUN_ID),用于把一次运行中所有页面级诊断与基准结果关联起来。
2.2 隔离上下文:withBenchmarkPage
需要隔离浏览器上下文(例如冷/热切换的每轮试验都要干净的环境)时,使用 withBenchmarkPage(benchmark.ts#L100-L118):它自行 browser.newContext()、创建页面、安装同一套诊断生命周期,并在结束时关闭 context。会话 tab 切换基准正是用它包裹每一轮 cold/hot 试验,保证 5 轮结果互不污染:
results[mode].push(
await withBenchmarkPage(browser, `session-tab-switch-${mode}-${run}`, (page) => trial(page, mode), testInfo),
)
三、Chrome Trace:页面全生命周期的标准轨迹采集
设置 OPENCODE_PERFORMANCE_TRACE_DIR 后,每个基准页面都会自动产出标准 Chrome DevTools trace(无需在测试内写任何 trace 代码),可直接加载进 Chrome DevTools 的 Performance 面板:
OPENCODE_PERFORMANCE_TRACE_DIR=/tmp/opencode-performance-traces \
bunx playwright test --config e2e/performance/playwright.config.ts \
timeline/session-tab-switch-benchmark.spec.ts
3.1 采集实现:CDP Tracing + ReturnAsStream
chrome-trace.ts 的 startChromeTrace 通过 CDP 会话采集轨迹,要点(chrome-trace.ts#L21-L67):
- 类别配置:默认排除
-*前缀的类别,包含devtools.timeline、v8.execute、latencyInfo、disabled-by-default-devtools.timeline.stack等;当OPENCODE_PERFORMANCE_SELECTOR_TRACE=1时,额外开启disabled-by-default-blink.debug与disabled-by-default-devtools.timeline.invalidationTracking——这是 selector 统计(INP 类分析的前置数据)所需的增强采集; - 传输模式:
Tracing.start使用ReturnAsStream,停止后经Tracing.tracingComplete拿到流句柄,再用IO.read分块落盘(chrome-trace.ts#L84-L95); - 完整性保证:先写
${file}.partial,若 Chromium 报告dataLossOccurred则保留 partial 文件并抛错Chrome trace lost data,成功才rename为正式文件——对应 Puppeteer 官方 tracing 的默认生命周期与失败语义; - 文件命名:
${runID}-${sanitized-name}-${sha256(name)[0:8]}-${nonce}[-selectors].json,trace 路径随后出现在BENCHMARK_PAGE行中,供命令行工具消费:
bunx devtools-tracing stats <trace-path-from-BENCHMARK_PAGE>
README 明确边界:Chrome trace 是浏览器级、页面全生命周期的诊断数据;而场景指标(如首帧可见时间、稳定时间)使用的是更窄的显式命名观察窗口。二者的分工写进了 AGENTS.md:"不要跨 harness、probe 和 trace 重复测量;自定义探针只用于产品特有度量"。
INP 分析需要包含受支持的导航/交互 insight 的 trace;selector 统计则需要以 OPENCODE_PERFORMANCE_SELECTOR_TRACE=1 捕获。
3.2 去帧率限制的 uncapped 配置
playwright.uncapped.config.ts 在默认配置上追加启动参数 --disable-frame-rate-limit、--disable-gpu-vsync,用于显式的去上限诊断(例如观察 RAF 间隔分布时排除合成器节流干扰)。README 同时声明:原生产品基准应使用默认 Playwright 配置。
四、剖析开关、环境变量与指标语义
4.1 剖析开关(默认关闭)
CPU 与高负载视觉剖析默认禁用,以保持基准负载的确定性:
| 环境变量 | 作用 |
|---|---|
TIMELINE_CPU_PROFILE=1 |
同时开启 CPU profile 与视觉剖析 |
TIMELINE_VISUAL_PROFILE=0 |
在已开启 CPU profile 时仅保留 CPU 剖析 |
OPENCODE_PERFORMANCE_TRACE_DIR |
开启全页面 Chrome trace 采集 |
OPENCODE_PERFORMANCE_SELECTOR_TRACE=1 |
trace 中追加 selector/失效跟踪类别(INP、selector 统计所需) |
OPENCODE_PERFORMANCE_RUN_ID |
诊断行 runID,默认由配置自动生成 |
这与 AGENTS.md 的原则"当详细剖析会改变负载行为时保持其 opt-in"一致——视觉剖析的截图读取会扰动计时,因此默认关闭。
4.2 流式场景的负载参数
流式时间线场景的默认值与可调项(对照 session-timeline-benchmark.spec.ts#L102-L110 的解析代码):
| 环境变量 | 默认值 | 含义 |
|---|---|---|
TIMELINE_DELTA_COUNT |
160 | 流式增量事件数量;覆盖时会随结果 context 上报 |
TIMELINE_HISTORY_TURNS |
320 | 会话历史轮数(DOM 规模) |
TIMELINE_EVENT_BATCH |
1 | 事件投递批大小;覆盖时同样进入 context |
TIMELINE_CPU_THROTTLE |
30 | 30x CPU 节流系数 |
TIMELINE_COMPLETION_TIMEOUT_MS |
420000 | 流式完成等待超时(测试超时在其上再加 60s) |
TIMELINE_MINIMAL |
— | =1 时使用最小化负载布局 |
SESSION_TAB_SWITCH_RUNS |
5 | tab 切换基准每种模式轮次 |
4.3 指标语义的三条重要边界
README 对流式基准的指标给出了明确的解释边界,值得在引用数据时牢记:
- 30x CPU 节流是确定性压力画像,不是模拟真实低端设备;同样,stability 套件中的 4x CPU 压力也如此声明;
- 这些指标是主线程回调诊断:吞吐、RAF 回调间隔分布、帧预算等价值、长任务,都度量到"渲染器观察到的完成时间"与"最终几何稳定"为止——不是合成器呈现或掉帧测量;探针禁用的视觉/几何指标会以
null呈现; - 基准不断言机器相关的性能预算。断言只验证"场景完成 + 指标采集完成"。重绘状态做游程分组(run-length grouping)用于压缩,但每条原始观察时间戳与原始 mutation 批次、布局偏移都无损保留——对应 AGENTS.md 的"保留原始诊断数据或采用无损表示"。
五、与 timeline-stability 套件的分工
e2e/performance 下还有第二个独立配置目录 timeline-stability,通过 bun run test:stability 运行(见 packages/app/package.json#L30):它对时间线布局连续性做可判定的合约测试(锚点保持、行序、披露状态在虚拟化中的保持、键盘/滚轮所有权一致等),并对失败场景保留 video.webm、trace.zip、失败截图、采样 DOM/布局 trace JSON 等证据。
两者分工清晰:stability 套件负责"布局连续性合约是否被违反"的 pass/fail 判定;performance 套件负责"交互到底花了多久、主线程回调分布如何"的诊断量化。前者的判明边界(不检查每个合成器呈现像素、不覆盖特定刷新率/设备)在其 README 中有专门声明。
六、方法论依据与扩展边界
套件的方法论直接对齐浏览器与框架生态的官方建议:Electron 官方教程推荐用重复的 Chrome DevTools / Chrome Tracing 测量做性能回归;Chrome DevTools 官方文档推荐用 Performance 录制刻画运行时工作;Playwright 官方将 trace 定位为测试调试工具而非渲染进程剖析工具——因此本套件选择"Playwright 管场景编排与完成检查、Chrome trace 管通用浏览器剖析、自定义探针只做产品特有测量"的三层结构。
对于未来的打包版(Electron)基准,README 给出了明确路线:若需要主进程与多进程归因,应使用 Electron 官方 contentTracing API,而不是往这套渲染器 harness 里加自制的进程级插桩。当前套件剖析的对象始终是 Chromium 中的共享 app 渲染进程。
参考路径汇总
- 套件说明:packages/app/e2e/performance/README.md
- 共享 fixture 与诊断协议:packages/app/e2e/performance/benchmark.ts
- Chrome trace 采集实现:packages/app/e2e/performance/chrome-trace.ts
- 基准 Playwright 配置:packages/app/e2e/performance/playwright.config.ts、uncapped 配置
- 场景基准:会话 tab 切换、流式时间线、首页 tab 导航、首次导航、review 面板扩展
- 设计原则:packages/app/e2e/performance/AGENTS.md
- 布局连续性合约套件:packages/app/e2e/performance/timeline-stability/README.md
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00