diff --git a/docs/EVIDENCE.zh.md b/docs/EVIDENCE.zh.md index d52b5ea..30013db 100644 --- a/docs/EVIDENCE.zh.md +++ b/docs/EVIDENCE.zh.md @@ -2,41 +2,31 @@ [English](EVIDENCE.md) · [中文](EVIDENCE.zh.md) -`docs/reports/` 里的每个数字都来自某次 run 自己的产物。这页是两者之间的索引:哪些 -文件固定了一次 run 的身份,哪些压缩包装着它的原始结果行,以及每个包的 sha256 应该 -是多少。 +`docs/reports/` 里的每个数字,都来自某次 run 自己留下的产物。这一页就是"报告 ↔ 产物 ↔ 下载包"三者之间的索引:哪些文件固定了一次 run 的身份(给一次 run 打上指纹)、哪些压缩包装着它的原始结果行、每个包的 sha256 校验和应该是多少。 -链条是 报告 → 本索引 → Release 资产。小文件随仓库分发,大产物不进 git 历史。 +证据链条是:**报告 → 本索引 → Release 发布资产**。小文件跟着仓库一起分发,大产物不进 git 历史。 ## 随仓库分发的部分 -`docs/evidence//` 放的是给一次 run 打指纹的文件,合计约 4.7 MB,不下载任何 -东西就能核对一个已发布的数字是由什么产出的。 +`docs/evidence//` 放的是给一次 run 打指纹的文件,合计约 **4.7 MB**。不看报告、不下载任何东西,光凭这些文件就能核对"某个已发布数字到底是由什么产出的"。 | 文件 | 固定了什么 | |:---|:---| -| `run_manifest.json` | 引擎版本与 sha256、各生态的 driver pin、runner 源码树 / fixture 树 / 每个编译 adapter 的摘要、seed,以及完整 argv | -| `scores.json` | 按评测轴和 subset 的计数、`score_eligible`、failure class 与 failure origin 的统计 | -| `host_summary.json` | 主机遥测的时间窗、采样数,以及机器当时是否被抢占(`polluted`) | -| `resource_summary.json` | 校准后的资源分布、observer-effect 的 A/B 对比,以及 `resource_comparison_eligible`(仅资源轮) | -| `cold_start.jsonl` | 冷启动诊断,与 warm 画像分开保存(仅资源轮) | +| `run_manifest.json` | 引擎版本与 sha256、各生态的 driver pin、runner 源码树 / fixture 树 / 每个编译 adapter 的摘要、seed,以及完整的启动参数 | +| `scores.json` | 按评测维度和子集的计数、`score_eligible`、failure class 与 failure origin 的统计 | +| `host_summary.json` | 主机遥测的时间窗、采样数,以及机器当时是否被抢占(`polluted`) | +| `resource_summary.json` | 校准后的资源分布、观测者效应的 A/B 对比,以及 `resource_comparison_eligible`(仅资源轮) | +| `cold_start.jsonl` | 冷启动诊断,与热启动画像分开保存(仅资源轮) | -报告里有两张表可以直接对着这些文件核。四引擎的评测轴表里 Chrome 在 L1 是 -5214/5220、L2 是 192/192,就是 `scores.json` 里的值;资源卡片里 Chrome 的 CPU 中位数 -687 ms,对应 `by_engine.chrome.metrics.cpu_total_ms.median` 的 686.948。 +报告里有两张表可以直接对着这些文件核验。例如:四引擎报告的评测轴表里,Chrome 在 L1 是 5214/5220、L2 是 192/192,就是 `scores.json` 里的原值;资源卡片里 Chrome 的 CPU 中位数 687 ms,对应 `by_engine.chrome.metrics.cpu_total_ms.median` 里的 686.948。 -头条的任务级通过率需要结果行本身,因为一道题只有所有尝试都通过才算通过。那些行在下 -面的压缩包里。 +头条的"任务级通过率"需要原始结果行才能算——因为一道题要**所有尝试都通过**才算通过,而这个信息只存在下面的压缩包里。 -## Release 资产 +## 发布资产 -挂在 [evidence-20260812](https://github.com/lexmount/Lexbench-Headless-Browser/releases/tag/evidence-20260812) -上。这个 tag 命名的是采集时间而不是代码状态:这批 run 采集于 2026-08-12 和 08-13, -而 tag 指向的那棵树比它们更晚。 +挂在 [evidence-20260812](https://github.com/lexmount/Lexbench-Headless-Browser/releases/tag/evidence-20260812) 这个 Release tag 上。注意:这个 tag 是按**采集时间**而不是代码状态命名的——这批 run 采集于 2026-08-12 和 08-13,而 tag 指向的那棵树比它们更晚。 -每个 `evidence-*` 包含一次 run 的 `results.jsonl`、`host_telemetry.jsonl` 和 -`scorecard.md`;每个 `artifacts-*` 包含该次 run 每个 attempt 的原始协议日志,第三方 -因此可以审计某一次失败的协议交互,而不只是看结果行。 +每个 `evidence-*` 包包含某次 run 的 `results.jsonl`、`host_telemetry.jsonl` 和 `scorecard.md`;每个 `artifacts-*` 包则包含该次 run 每一次尝试的**原始协议日志**——第三方因此可以审计某一次失败到底跟浏览器做了什么交互,而不只是看结果行。 | 资产 | 内容 | 下载 | 解压后 | |:---|:---|---:|---:| @@ -47,13 +37,11 @@ | `artifacts-resource_baseline_20260812.tar.gz` | 59,366 个文件 | 7.1 MiB | 111 MiB | | `artifacts-resource_engine_20260812.tar.gz` | 70,505 个文件 | 13.1 MiB | 171 MiB | -Kitesurf lane 另有两个资产挂在同一个 Release 上,由 -[`kitesurf-eval` 分支的 `docs/EVIDENCE.md`](../../../tree/kitesurf-eval/docs/EVIDENCE.md) -索引。 +Kitesurf 通道另有两个资产挂在同一个 Release 上,由 [`kitesurf-eval` 分支的 `docs/EVIDENCE.md`](../../../tree/kitesurf-eval/docs/EVIDENCE.md) 索引。 -### sha256 +### sha256 校验和 -``` +```text 3052461b458581c8da620d55c3741d18dc50693d78c7a6ebeffb2241251f12f9 evidence-four_engine_full_20260812.tar.gz 55d4f98f4f386ec2f7856d0b96b2c8f9e9838d872f00f03f1589e6e8a94f51a5 evidence-resource_baseline_20260812.tar.gz 935c6232e5598cddd25cebc11ed2c35e03e43b0d3f77c1bc011787f07c0b8162 evidence-resource_engine_20260812.tar.gz @@ -62,7 +50,7 @@ dbf474352f8db054cf61870bc8b0792f8b1d48aa38b8e4ee2a31b337e8bd0ab9 artifacts-reso 6c67855929d185d1a41fc59920d32c62e459a15fd2719c86f611582f8b83b349 artifacts-resource_engine_20260812.tar.gz ``` -核对下载: +核对已下载的文件: ```bash sha256sum -c <<'EOF' @@ -70,25 +58,22 @@ sha256sum -c <<'EOF' EOF ``` -打包时固定了成员顺序、归属和 mtime,所以同一次 run 重新打包能得到相同的 sha256。 +打包时固定了成员的顺序、归属和 mtime(修改时间),所以同一份 run 重新打包能得到相同的 sha256——指纹可复现。 ## 重新生成报告 -把某次 run 的 `evidence-*` 解压到它的 `docs/evidence//` 旁边,让生成器同时看 -到结果行和 manifest,然后: +把某次 run 的 `evidence-*` 包解压到它自己的 `docs/evidence//` 旁边,让生成器同时看到结果行和 manifest,然后跑: ```bash python3 tools/report_four_engine.py runs/four_engine_full_20260812 \ -o docs/reports/four-engine-report-20260812.md ``` -两份已发布的报告都用这种方式从 Release 压缩包里重新生成过,输出与仓库里的副本逐字节 -相同。五引擎报告在 `kitesurf-eval` 分支生成,它的输入在那边。 +两份已发布报告都用这种方式从 Release 压缩包重新生成过,输出与仓库里的副本**逐字节相同**。五引擎报告在 `kitesurf-eval` 分支生成,它的输入在那边。 ## 发布前去掉了什么 -Run 产物会记录这一轮实际用过的路径和 origin,这是本地审计线索而不是证据。 -`tools/scrub_release_paths.py` 在打包时改写三类信息: +Run 产物会记录这一轮实际用过的路径和 origin(来源主机)——这些是**本地审计线索,不是证据**。`tools/scrub_release_paths.py` 会在打包时改写三类信息: | 信息 | 改写为 | |:---|:---| @@ -96,13 +81,9 @@ Run 产物会记录这一轮实际用过的路径和 origin,这是本地审计 | 这一轮 fixture 由哪个 host 提供 | `` 或 `` | | 带非 UTC 偏移的 ISO-8601 时间戳 | 同一时刻的 `+00:00` 表示 | -fixture origin 是从产物自身发现的:verification 报告里的 `base_url`、run summary 里 -的 `scope.fixture_base_url`,所以这个脚本本身不写死任何 host。 +fixture origin 是从产物自身发现的——verification 报告里的 `base_url`、run summary 里的 `scope.fixture_base_url`——所以这个脚本本身不写死任何 host。 -指纹链一律不动:引擎和 adapter 的 sha256 保持原样,只有"通向某个二进制的路径"被当作 -本地信息处理。四引擎那次 run 改写了 1,338 个文件里的 5,028 处路径,origin 改写为 -零,因为它的 fixture 由 `127.0.0.1` 提供。清洗后的树重新生成两份报告仍然逐字节相同, -这就是"改写没有动到证据"的验证方式。 +指纹链一律不动:引擎和 adapter 的 sha256 保持原样,只有"指向某个二进制的路径"被当作本地信息处理。四引擎那次 run 一共改写了 **1,338 个文件里的 5,028 处路径**;origin 改写为零,因为它的 fixture 由 `127.0.0.1` 提供。清洗后的树重新生成两份报告仍然逐字节相同——这就是"改写没有动到证据"的验证方式。 随时复查一棵树: @@ -111,6 +92,4 @@ python3 tools/scrub_release_paths.py runs/ --check python3 tools/scrub_release_paths.py build/release-runs/ --check --origins-from runs/ ``` -第二种形式用来检查已经清洗过的树。origin 是从产物里发现的,而清洗过的产物已经不再 -写出任何 origin,所以这种检查需要原始的树来告诉它要找什么。如果让脚本在没有这个参数 -的情况下检查一棵已清洗的树,它会拒绝执行,而不是给出一个根本没验过的"干净"结论。 +第二种形式用来检查**已经清洗过的**树。origin 是从产物里发现的,而清洗过的产物已经不再写出任何 origin,所以这种检查需要原始的树来告诉它"要去找什么"。如果让脚本在没有 `--origins-from` 的情况下检查一棵已清洗的树,它会**直接拒绝执行**——而不是给出一个根本没验过的"干净"结论。 diff --git a/docs/REPRODUCE.zh.md b/docs/REPRODUCE.zh.md index 6383a42..9cef426 100644 --- a/docs/REPRODUCE.zh.md +++ b/docs/REPRODUCE.zh.md @@ -2,7 +2,7 @@ [English](REPRODUCE.md) · [中文](REPRODUCE.zh.md) -报告里的每一个数字都能追溯到三次 run 之一。这一页记录它们的精确参数、一次 run 怎么变成一份报告,以及你的复现结果对不上时该查什么。 +报告里的每一个数字,都能追溯到下面三次 run 之一。这一页记录它们的精确参数、一次 run 怎么变成一份报告,以及你的复现结果对不上时该查什么。 ## 三次已发布的 run @@ -12,9 +12,9 @@ | `resource_baseline_20260812` | 资源 A 轮 | `l1.raw_cdp` + `l2.web_platform`,557 道 | 5 | 1 | 关 | | `resource_engine_20260812` | 资源 B 轮 | 同一批 557 道 | 5 | 1 | 开 | -运行参数:bench `2026.08.02-v0_4.1`,seed `official20260709`,`--score-mode independent --chrome-baseline best_effort`,引擎 `chrome,moli,lightpanda,obscura`。引擎 pin(版本与 sha256)列在各报告的溯源表里,由 `doctor` 强制校验;二进制的获取位置——[Moli](https://github.com/lexmount/moli)、[Chrome for Testing](https://googlechromelabs.github.io/chrome-for-testing/)、[Lightpanda](https://github.com/lightpanda-io/browser)、[Obscura](https://github.com/h4ckf0r0day/obscura)——与放置路径见 [RUNNING.zh.md](RUNNING.zh.md)。下面的命令逐字取自各 run 的 `run_manifest.json` 中记录的 `argv`。 +三次 run 的共同参数:bench 版本 `2026.08.02-v0_4.1`、seed `official20260709`、`--score-mode independent --chrome-baseline best_effort`、引擎 `chrome,moli,lightpanda,obscura`。每个引擎的 pin(版本与 sha256)列在各报告最后的溯源表里,由 `doctor` 强制校验;二进制的获取位置——[Moli](https://github.com/lexmount/moli)、[Chrome for Testing](https://googlechromelabs.github.io/chrome-for-testing/)、[Lightpanda](https://github.com/lightpanda-io/browser)、[Obscura](https://github.com/h4ckf0r0day/obscura)——与放置路径见 [RUNNING.zh.md](RUNNING.zh.md)。下面的命令逐字取自各 run 的 `run_manifest.json` 里记录的 `argv`(实际启动参数)。 -功能run: +**功能 run:** ```bash python3 -m runner.run run \ @@ -24,7 +24,7 @@ python3 -m runner.run run \ --run-id four_engine_full_20260812 --provenance-level minimal ``` -资源run,按顺序执行(B 轮必须引用 A 轮): +**资源 run**(必须按顺序执行,B 轮要引用 A 轮): ```bash python3 -m runner.run run \ @@ -46,38 +46,38 @@ python3 -m runner.run run \ --run-id resource_engine_20260812 --provenance-level full ``` -观测干扰阈值显式设为 `20` 而不是默认的 10:Chrome 的进程树超过一百个进程,每次 attempt 首尾两次全树 PSS 扫描是测量本身的固有成本;报告里记录了这个选择,并给出实际的任务时长干扰 ≤0.87%。 +为什么观测干扰阈值用 `20` 而不是默认的 `10`:Chrome 的进程树超过一百个进程,每次尝试首尾各做一次全树 PSS 扫描,本身就是测量负担带来的固有成本。报告里记录了这个选择,并给出实际的任务时长干扰 ≤0.87%。 -资源两轮必须在同一台机器上跑,且中途没有其它负载。只有两轮之间机器状态保持不动,A/B 对比才有意义。 +**注意:** 资源两轮必须在**同一台机器**上跑,且中途不能有别的负载。只有两轮之间机器状态保持不变,A/B 对比才有意义。 ## 从 run 到报告 -报告是生成出来的,不是写出来的: +报告是**生成**出来的,不是手写出来的: ```bash python3 tools/report_four_engine.py runs/four_engine_full_20260812 \ -o docs/reports/four-engine-report-20260812.md ``` -生成器读 `results.jsonl` 并重新计算一切。对同一个 run 目录跑两遍,输出逐字节相同——这也是验证已发布报告没被手改过的方法:重新生成,然后 `diff`。 +生成器读 `results.jsonl` 并重新计算一切。对同一个 run 目录跑两遍,输出**逐字节相同**——这也是验证已发布报告没被手改过的方法:重新生成,然后 `diff`。 ## 复现凭什么可比 - `--run-id` 逐字命名 run 目录,所有记录的时间戳都是 UTC。 -- 每次 attempt 的种子由 `sha256(seed:task_id:attempt)` 推导,与引擎顺序和壁钟时间无关。 -- `run_manifest.json` 对 runner 源码树、fixture 树和编译后的 adapter 二进制做了摘要。对比两次 run 之前先对比这些摘要:摘要不同,说明框架本身不同。 +- 每次尝试的随机种子由 `sha256(seed:task_id:attempt)` 推导,与引擎顺序和壁钟时间无关。 +- `run_manifest.json` 对 runner 源码树、fixture 树和编译后的 adapter 二进制做了摘要。对比两次 run 之前,先对比这些摘要:摘要不同,说明框架本身不同。 - 结果行不含任何主机绝对路径;每次启动的临时目录记录为 ``。 - `--provenance-level minimal` 保留硬件与内核事实、去掉 cgroup 路径和 CPU 亲和性这类部署指纹。资源两轮用的是 `full`,因为校准审计需要部署细节;功能轮以 `minimal` 发布。 ## 证据放在哪 -run 目录不入库(`runs/` 在 gitignore 里;三次已发布 run 未压缩合计超过 1 GB)。已发布的证据以压缩包形式挂在仓库的 GitHub Releases 上,每个包内含 `results.jsonl`、`run_manifest.json` 和摘要文件,sha256 校验和随包列出。克隆仓库得到框架和报告;要审计或重新生成时再取证据包。 +run 目录不入库(`runs/` 在 gitignore 里;三次已发布 run 未压缩合计超过 1 GB)。已发布的证据以压缩包形式挂在仓库的 GitHub Releases 上:每个包内含 `results.jsonl`、`run_manifest.json` 和摘要文件,sha256 校验和随包列出。克隆仓库得到的是框架和报告;要审计或重新生成结果时,再去取证据包。 ## 你的数字对不上时 在得出任何关于浏览器的结论之前,先沿这架梯子往下查: -1. 对比 `run_manifest.json` 的摘要和引擎 sha256 与已发布值。pin 不同意味着你测的是不同的软件——这是一个答案,不是一个错误。 +1. 对比 `run_manifest.json` 的摘要和引擎 sha256 与已发布值。**pin 不同意味着你测的是不同的软件**——这是一个答案,不是一个错误。 2. 确认 `doctor` 是绿的,并且结果里没有异常数量的 `infra`。身份失败是环境问题。 -3. 功能分层面,flake 级的小差异表现为某些题只过了 3 次中的 2 次;全 attempt 通过的规则让 headline 数字对真实的不稳定敏感,这是有意的。 -4. 资源数字层面,确认你自己的 B 轮里 `resource_comparison_eligible: true`。没过校准门的轮次产出的数字跟任何人的都不可比,包括我们的。资源数字也永远不外推到 557 道之外。 +3. 功能分层面,flake 级的小差异表现为某些题只过了 3 次中的 2 次;"所有尝试都通过才算通过"的规则让头条数字对真实的不稳定敏感,这是有意的设计。 +4. 资源数字层面,确认你自己的 B 轮里 `resource_comparison_eligible: true`。没过校准门的轮次产出的数字,跟任何人的都不可比(包括我们的)。资源数字也永远**不外推**到 557 道之外。 diff --git a/docs/RESULTS.zh.md b/docs/RESULTS.zh.md index 1d55c60..3e9bce7 100644 --- a/docs/RESULTS.zh.md +++ b/docs/RESULTS.zh.md @@ -2,13 +2,13 @@ [English](RESULTS.md) · [中文](RESULTS.zh.md) -报告负责给出数字,这一页负责定义数字的含义、边界,以及它们支撑不了什么结论。 +报告负责给出数字,这一页负责定义**数字的含义、边界,以及它们支撑不了什么结论**。 -## Headline 数字是什么 +## 关键数字是什么 -Headline 通过率把一道题计为通过的条件是全部 k 次 attempt 都通过(已发布 run 的 k=3)。过了两次、超时一次的题按失败计。让 headline 对不稳定敏感是有意的:agent 重试一个抖动的操作,消耗预算的方式和撞上硬失败没有区别。 +头条通过率计一道题通过的标准是:**全部 k 次尝试都通过**(已发布 run 的 k=3)。过了两次、超时一次,这道题按失败计。让头条数字对不稳定敏感是有意的:Agent 重试一个抖动的操作,消耗预算的方式跟撞上硬失败没有区别。 -每个引擎只按自己的 attempt 计分(`--score-mode independent`)。Chrome 是参照列,不是门。另一种模式 `--chrome-baseline required` 会把 Chrome 失败的题从所有引擎的分母中剔除,从而把 Chrome 那列构造性地推向 100%,这正是发布 run 采用 `best_effort` 的原因:Chrome 的 99.90% 是测出来的值,它挂掉的那两道题留在所有人的分母里。 +每个引擎只按自己的尝试计分(`--score-mode independent`)。Chrome 是参照列,不是及格线。另一种模式 `--chrome-baseline required` 会把 Chrome 失败的题从所有引擎的分母里剔除,等于把 Chrome 那列**保送**到接近 100%——这正是发布 run 采用 `best_effort` 的原因:Chrome 的 99.90% 是测出来的值,它挂掉的那两道题仍然留在所有人的分母里。 ## 状态分类 @@ -23,57 +23,62 @@ Headline 通过率把一道题计为通过的条件是全部 k 次 attempt 都 | `crash` | 引擎进程死亡 | 计 | | `infra` | 身份门或环境失败,引擎没有被有效连接 | 不计;该行划归框架侧 | -失败行还带 `failure.class` 和 `failure.origin`,报告因此能把一次失误归因到协议面、页面语义、driver 栈或框架自身,而不是只给一个裸计数。 +失败的行还带 `failure.class` 和 `failure.origin`,报告因此能把一次失误归因到**协议面、页面语义、driver 栈或框架自身**,而不是只给一个裸计数。 ## L1 轴:同一个行为,13 个生态 -L1 刻意让同一行为跨 driver 重复,因为 driver 兼容性正是被测量的维度。1,740 道 L1 里的 1,233 道由 116 个与 driver 无关的 scenario spec 展开而来,所以当一个引擎在 Puppeteer 下通过、在 Selenium 下失败时,这个差值本身就是发现。 +L1 刻意让同一个行为跨 driver 重复——因为 **driver 兼容性正是被测量的维度**。1,740 道 L1 里的 1,233 道,由 116 个**与 driver 无关**的场景定义展开而来。所以当一个引擎在 Puppeteer 下通过、在 Selenium 下失败时,这个差值本身就是发现。 -整列为零要读作 bootstrap 失败,而不是 92 次独立失误:这个栈对该引擎连会话握手都完成不了,握手之后的所有题都够不着。报告会把每个零列归因到根因。 +**整列为零要读成"启动级失败",而不是 92 次独立失误**:说明这个栈对该引擎连会话握手都完成不了,握手之后的题全都够不着。报告会把每个零列归因到根因。 -已发布 run 还显示信号方向是混合的,这本身就是关于 bench 的证据:Moli 总分领先,但 Lightpanda 在六个 subset 上高于 Moli(`l1.puppeteer` 114:96、`l1.agent_browser_scenarios` 80:66、`l1.cdp_use`/`l1.chrome_remote_interface`/`l1.stagehand` 76:69、`l1.agent_browser_tool` 68:64)。一套为某个引擎量身挑选的任务集不会产生这种模式。 +已发布 run 还显示信号方向是**混合的**——这本身就是关于测试集的证据:Moli 总分领先,但 Lightpanda 在六个 subset 上高于 Moli(`l1.puppeteer` 114:96、`l1.agent_browser_scenarios` 80:66、`l1.cdp_use`/`l1.chrome_remote_interface`/`l1.stagehand` 76:69、`l1.agent_browser_tool` 68:64)。一套专门为某个引擎量身挑选的任务集,不会产生这种模式。 -## L2 轴:按 capability 计,不按题数计 +## L2 轴:按能力项计,不按题数计 -L2 按行为判语义。fixture 跑在框架自己的服务器上,由服务端 grader 检查可观察结果(DOM 状态、存储状态、网络服务端状态或工作流结果),而不是信协议的回声。 +L2 按行为判语义。fixture 跑在框架自己的服务器上,由**服务端判题器**检查可观察结果(DOM 状态、存储状态、网络服务端状态或工作流结果),而不是信协议的回声(CDP 报成功但页面没变化)。 -原始任务行不是分母。188 道 L2 题经 [`config/l2_semantic_capabilities.json`](../config/l2_semantic_capabilities.json) 映射到 72 个 capability,每道题承担三种角色之一: +原始任务行不是分母。188 道 L2 题经 [`config/l2_semantic_capabilities.json`](../config/l2_semantic_capabilities.json) 映射到 **72 个能力项**,每道题承担三种角色之一: -- `semantic_probe` 计入所属 capability 的判定,capability 通过的条件是分配给它的所有计分 probe 全部通过; -- `driver_cross_check` 单独报告,不增加分母单元; -- `diagnostic` 提供失败定位证据,不增加分母单元。 +- `semantic_probe`:计入所属能力项的判定。一个能力项通过的条件 = 分配给它的**所有**计分 probe 全部通过; +- `driver_cross_check`:单独报告,不增加分母单元; +- `diagnostic`:提供失败定位证据,不增加分母单元。 -这个合取判定严格,但权重是一。六个向量的 WebCrypto digest 家族能定位到具体算法的 bug,却不会把一个实现缺口放大成六次 headline 失败;反过来,capability 也不会靠"运气好的那部分 probe"通过。在已发布 run 里,这套映射产出报告 L2 轴上的 192 个计分单元。 +这个"全对才算过"(合取)判定很严格,但**权重是一**:六个向量的 WebCrypto digest 家族能定位到具体算法的 bug,却不会把一个实现缺口放大成六次头条失败;反过来,能力项也不会靠"运气好的那一部分 probe"蒙混过关。在已发布 run 里,这套映射产出报告 L2 轴上的 **192 个计分单元**。 -两道护栏保证稳定。部分选题的 run 如果只选中了某 capability 的一部分 probe,该 capability 直接标记为 `missing`,而不是拿一半证据悄悄算一个分。run manifest 会对 capability map 做快照(路径 + sha256),map 演进之后旧 run 依然可解释。 +两道护栏保证稳定: -多步工作流任务的判定是双门的:driver 声明的步骤检查要全部完成,fixture 服务器还要按预期答案注册表验收提取出的最终答案。driver 侧的相等断言从不单独决定一个工作流的成绩。 +- **部分选题不算分**:某次 run 如果只选中一个能力项的部分 probe,该能力项直接标记为 `missing`,而不是拿一半证据悄悄算一个分。 +- **配置可追溯**:run manifest 会为能力映射表做快照(路径 + sha256);映射表演进之后,旧 run 依然可解释。 + +多步工作流任务的判定是**双门**的:driver 声明的步骤检查要全部完成,fixture 服务器还要按预期答案注册表验收提取出的最终答案。driver 侧的相等断言**从不单独决定**一个工作流的成绩。 ## 资源数字怎么读 -资源数字来自单独的 A/B 校准轮,契约见 [resource-cost.zh.md](resource-cost.zh.md)、复现步骤见 [REPRODUCE.zh.md](REPRODUCE.zh.md)。阅读时注意: +资源数字来自单独的 A/B 校准轮:契约见 [resource-cost.zh.md](resource-cost.zh.md)、复现步骤见 [REPRODUCE.zh.md](REPRODUCE.zh.md)。阅读时注意: -- 统计在四个引擎共同通过的 1,045 个 task-attempt 交集上进行,失败的题从不以"便宜的失败"计入任何引擎的账。 -- 数字描述的是 557 道的资源任务集,不外推到全量。 +- 统计在四个引擎共同通过的 **1,045 个任务×尝试交集**上进行;失败的题从来不按"便宜的失败"计入任何引擎的账。 +- 数字描述的是 **557 道**的资源任务集,不外推到全量。 - 实心值是中位数;资源卡片里还有 p95、PSS 增量、进程数和 fixture 流量。 -- 远程引擎在测量机上没有进程树。空单元格的意思是无法测量,永远不是零。 +- 远程引擎在测量机上**没有进程树**。空单元格的意思是"无法测量",永远不是零。 ## 五引擎对比 -main 分支报告四个本地固定二进制。[`kitesurf-eval`](https://github.com/lexmount/Lexbench-Headless-Browser/blob/kitesurf-eval/README.zh.md) 分支加入远程端点 Kitesurf,并在显式定义的可比任务子集上发布对比:先剔除跨远程边界后失败无法归因的 subset,再进一步剔除端到端系统性阻塞的 subset。子集定义、裁定规则和五引擎表都在那个分支的报告里;一句话版本是:远程端点只有在显式声明的分母之内才可比。 +main 分支报告四个本地固定二进制。[`kitesurf-eval`](https://github.com/lexmount/Lexbench-Headless-Browser/blob/kitesurf-eval/README.zh.md) 分支加入远程端点 Kitesurf,并在**显式定义的可比任务子集**上发布对比:先剔除"跨远程边界后失败无法归因"的 subset,再进一步剔除端到端系统性阻塞的 subset。子集定义、裁定规则和五引擎表都在那个分支的报告里;一句话版本是:**远程端点只有在显式声明的分母之内才可比。** ## 为什么测这些机制 -引擎团队自己发布的 benchmark,第一个被问的问题一定是:题是不是挑着对自家有利的?任务集的覆盖面来自对 pinned 版 Playwright、Puppeteer、agent-browser 等栈实际调用面的调研,再按真实框架会打到的路径补题;每道题必须在 Chrome 上通过才有入库资格。上面那组混合的 subset 信号就是可观察的结果。 +引擎团队自己发布的 benchmark,第一个被问的问题一定是:**题是不是挑着对自家有利的?** + +任务集的覆盖面来自对 pinned 版 Playwright、Puppeteer、agent-browser 等栈**实际调用面**的调研,再按真实框架会打到的路径补题;每道题必须在 Chrome 上通过才有入库资格。上面那组混合的 subset 信号,就是可观察的结果。 -L2 选的机制是生产页面真实依赖的,配第三方使用数据: +L2 选的机制是生产页面真实依赖的,并配了第三方使用数据: -- 现代选择器是日常承重面。Chrome use counter 显示截至 2026-06 有 51.7% 的页面加载命中使用 `:has()` 的页面,一年前是 40.6%([chromestatus,bucket 4743](https://chromestatus.com/metrics/css/timeline/popularity/4743))。Project Wallace 2026 对头部站点首页 CSS 的爬取显示 41.3% 的样式表用了 `:has()`、76.8% 用了 `:nth-child`、53.2% 用了 `:nth-of-type`([The CSS Selection 2026](https://www.projectwallace.com/the-css-selection/2026))。选择器算错等于内容或可见性状态算错,不是外观瑕疵。 -- 客户端存储是沉默的基础设施。Chrome 最后一批公开计数显示 19.3% 的页面加载执行了 IndexedDB 读、16.6% 执行了写([bucket 3023](https://chromestatus.com/metrics/feature/timeline/popularity/3023)、[bucket 3024](https://chromestatus.com/metrics/feature/timeline/popularity/3024));Firestore 的 web 离线持久化就是 IndexedDB([文档](https://firebase.google.com/docs/firestore/manage-data/enable-offline))。促成这批题的正是 agent 场景特有的失败方式:引擎对缺失的存储 API 优雅降级,页面照常渲染但数据为空,agent 会自信地给出错误答案而不是报错。只有存储语义题能暴露这一点。 -- driver 的交互原语直接消费 layout 和 computed style。Playwright 的 actionability 规则要求每次 click/fill 前有非空 bounding box、computed 可见性和 hit-target 检查([playwright.dev/docs/actionability](https://playwright.dev/docs/actionability))。样式或布局不完整的引擎丢掉的不是某一道题,而是框架自动等待与点击的底座。Lightpanda 自己的文档写明其 Web API 覆盖不完整([lightpanda.io/docs](https://lightpanda.io/docs/)),与它 L2 失误的聚集位置一致。 +- **选择器是日常承重面。** Chrome use counter 显示:截至 2026-06,51.7% 的页面加载命中使用了 `:has()` 的页面,一年前是 40.6%([chromestatus,bucket 4743](https://chromestatus.com/metrics/css/timeline/popularity/4743))。Project Wallace 2026 对头部站点首页 CSS 的爬取显示:41.3% 的样式表用了 `:has()`、76.8% 用了 `:nth-child`、53.2% 用了 `:nth-of-type`([The CSS Selection 2026](https://www.projectwallace.com/the-css-selection/2026))。**选择器算错 = 内容或可见性状态算错,不是外观瑕疵。** +- **客户端存储是沉默的基础设施。** Chrome 最后一批公开计数显示:19.3% 的页面加载执行了 IndexedDB 读、16.6% 执行了写([bucket 3023](https://chromestatus.com/metrics/feature/timeline/popularity/3023)、[bucket 3024](https://chromestatus.com/metrics/feature/timeline/popularity/3024));Firestore 的 web 离线持久化底层就是 IndexedDB([文档](https://firebase.google.com/docs/firestore/manage-data/enable-offline))。促成这批题的正是 Agent 场景特有的失败方式:引擎对缺失的存储 API **优雅降级**,页面照常渲染但数据为空,Agent 会自信地给出错误答案而不是报错——只有存储语义题能暴露这一点。 +- **driver 的交互原语直接消费 layout 和 computed style。** Playwright 的 actionability 规则要求每次 click/fill 前有非空 bounding box、computed 可见性和 hit-target 检查([playwright.dev/docs/actionability](https://playwright.dev/docs/actionability))。样式或布局不完整的引擎,丢掉的不是某一道题,而是**框架自动等待与点击的底座**。Lightpanda 自己的文档写明其 Web API 覆盖不完整([lightpanda.io/docs](https://lightpanda.io/docs/)),与它 L2 失误的聚集位置一致。 ## 边界 -- 截图、PDF 与光栅输出不在当前测量范围内。这是暂缓而不是永久结论:这条边界定于候选引擎普遍没有 paint 管线的时期,已列入重新评估。这里的一切都不测像素正确性。 -- 功能结果出自一组 pinned 引擎在一类机器上的表现。不同构建就是不同软件,比较数字之前先比较 manifest。 -- Chrome 的 99.90% 是测出来的,不是公理。它失败的两道题在报告里可见,且留在所有分母中。 +- **截图、PDF 与光栅输出不在当前测量范围内。** 这是**暂缓**而不是永久结论:这条边界划定于候选引擎普遍还没有 paint 管线的时期,已列入重新评估。这里的一切都不测像素正确性。 +- 功能结果出自一组 pinned 引擎在**一类机器**上的表现。不同构建就是不同软件——比较数字之前,先比较 manifest。 +- Chrome 的 99.90% 是测出来的,**不是公理**。它失败的那两道题在报告里可见,且留在所有分母中。 diff --git a/docs/RUNNING.zh.md b/docs/RUNNING.zh.md index 799fdaa..e34cbd0 100644 --- a/docs/RUNNING.zh.md +++ b/docs/RUNNING.zh.md @@ -1,8 +1,8 @@ -# 把 bench 跑起来 +# 把评测跑起来 [English](RUNNING.md) · [中文](RUNNING.zh.md) -本文档目标为让你在自己机器上完成一次完整 run。如果你的目标是精确复现已发布的数字,看完这页后接着读 [REPRODUCE.zh.md](REPRODUCE.zh.md)。 +这篇文档教你**在自己机器上完整跑一轮评测(run)**。如果你的目标是精确复现已发布的数字,看完这页之后,接着读 [REPRODUCE.zh.md](REPRODUCE.zh.md)。 ## 测哪些浏览器 @@ -13,11 +13,13 @@ | [Lightpanda](https://github.com/lightpanda-io/browser) | 候选 | 仓库 Releases,或按其构建文档自行编译 | | [Obscura](https://github.com/h4ckf0r0day/obscura) | 候选 | 仓库 Releases,或按其构建文档自行编译 | -`--engines` 接受这四个名字的任意逗号组合,默认全选。chromedriver 不是被测对象,它只是 Selenium 路由需要的桥。远程引擎(Kitesurf)不在 main 分支,见 `kitesurf-eval` 分支。 +角色列的含义:**候选** = 被测对象(这套题要检验的引擎);**参照列** = 标准答案(Chrome,不参与"替代"比较,只当"题能不能过"的底线和资源消耗的尺子)。 + +`--engines` 参数接受上面四个名字的任意逗号组合,默认全选。注意:**chromedriver 不是被测对象**,它只是 Selenium 连浏览器时需要的桥。远程引擎(Kitesurf)不在 main 分支,见 `kitesurf-eval` 分支。 ## 最短路径:先跑通一个引擎 -只想最快看到一个引擎跑起来,不需要四个二进制、也不需要 Go/Rust/Ruby 工具链(它们只服务对应的编译型 adapter subset)。放好一个引擎的二进制,装 Node 依赖,然后在裸 CDP subset 上跑 smoke: +只想最快看到一个引擎动起来?你不需要准备四个二进制,也不需要 Go/Rust/Ruby 工具链(它们只服务于对应的编译型 adapter 子集)。只要:放好一个引擎的二进制 → 装上 Node 依赖 → 在裸 CDP 子集上跑一遍冒烟测试(smoke,快速验证链路能不能通): ```bash npm ci @@ -25,15 +27,22 @@ python3 -m runner.run run --subset l1.raw_cdp --tag purpose.smoke \ --engines chrome --score-mode independent --seed smoke ``` -把 `chrome` 换成 `moli`、`lightpanda` 或 `obscura` 就是别的引擎。注意边界:单引擎 run 的结果行照常写入 `results.jsonl`(每道题的 pass/fail 都能看),但正式计分要求完整引擎名单——部分名单的 run 一律 `score_included: false`。它用来验证环境和观察单个引擎,不用来报数。 +把 `chrome` 换成 `moli`、`lightpanda` 或 `obscura`,就是测别的引擎。 + +**注意边界:** 单引擎 run 照常把结果写进 `results.jsonl`(每道题的 pass/fail 都能看),但**正式计分要求完整的引擎名单**——只测部分引擎的 run,一律标 `score_included: false`。这种跑法用来验证环境和观察单个引擎,**不能用来对外报数**。 ## 前提 -Linux(暂未支持其他平台),且启用 cgroup v2(资源遥测要读 cgroup 和进程树)。需要 Python 3.11+ 和 Node 20。Go、Rust、Ruby 只有编译型 adapter 才用得到:`chromedp` 和 `rod`(Go)、`chromiumoxide`(Rust)、`ferrum`(Ruby)。 +- 操作系统:**Linux**(暂不支持其他平台)。 +- 需要 **cgroup v2**:资源遥测要读 cgroup 和进程树来统计用量(cgroup 是 Linux 的资源统计机制)。 +- 需要 **Python 3.11+** 和 **Node 20**。 +- Go、Rust、Ruby 只有编译型 adapter 才用得到:`chromedp` 和 `rod`(Go)、`chromiumoxide`(Rust)、`ferrum`(Ruby)。 ## 1. 引擎二进制 -引擎二进制不入库,从上表「二进制从哪来」一列获取;复现已发布数字时,版本必须与报告溯源表里的 pin(版本 + sha256)一致。把一组固定版本的二进制放到 `build_artifacts/sets//` 下,附上 `set.json` 清单,然后激活: +引擎二进制**不入库**(不放进仓库),按上面「二进制从哪来」一列获取。**要复现已发布数字时,版本必须与报告溯源表里锁定的 pin(版本 + sha256 指纹)完全一致。** + +把一组固定版本的二进制放到 `build_artifacts/sets//` 下,附上 `set.json` 清单,然后激活这一组: ```bash tools/select_engine_set.sh @@ -49,12 +58,12 @@ build_artifacts/obscura/bin/obscura # 标准构建,非 stealth build_artifacts/chromedriver/bin/chromedriver ``` -`runner/run.py` 里的 `ENGINE_DEFS` 记录证据 pin(版本 + sha256);`build_artifacts/active-set.json` 按机器覆盖。任何 run 之前 `doctor` 都会拿 pin 逐个校验二进制——放错或被改过的二进制会大声失败,而不是安静地产出数字。 +`runner/run.py` 里的 `ENGINE_DEFS` 记录每台引擎的证据 pin(版本 + sha256);`build_artifacts/active-set.json` 可以在单台机器上覆盖默认设置。**任何 run 之前,`doctor` 都会拿 pin 逐个核对二进制**——放错或动过手脚的二进制会大声报错,而不是默默跑出一串数字。 ## 2. driver 依赖 ```bash -npm ci # Node 侧 driver,由 package-lock.json 固定 +npm ci # Node 侧 driver,由 package-lock.json 固定版本 pip install -e '.[drivers,dev]' # selenium + pydoll 的 pin,以及 pytest gem install ferrum -v 0.17.2 go build -C runner/scripts/adapters/chromedp_adapter -o chromedp_adapter . @@ -62,21 +71,29 @@ go build -C runner/scripts/adapters/rod_adapter -o rod_adapter . cargo build --manifest-path runner/scripts/adapters/chromiumoxide_adapter/Cargo.toml ``` -所有 driver 版本都固定在 `harness_pins.json` 里,`doctor` 会把实际安装的版本和 pin 逐一对照。 +所有 driver 的版本都固定在 `harness_pins.json` 里,`doctor` 会把实际安装的版本和 pin 逐一对照。 + +## 3. 跑前自检 -## 3. Inspection +正式开跑前,先过三道检查: ```bash -python3 -m runner.run doctor # 引擎能启动、身份校验、pin、adapter +python3 -m runner.run doctor # 引擎能启动吗?身份校验、pin、adapter 都过吗? python3 -m runner.run validate # 任务集完整性:1,928 道、18 个 subset python3 -m pytest test -q # 框架单元测试,不需要引擎二进制 ``` -每道门红了各代表什么:`doctor` 红是环境问题(缺二进制、pin 不匹配、adapter 没编译);`validate` 红说明磁盘上的任务集和 manifest 对不上;单测挂说明框架本身坏了。这三种情况都不该被解读成浏览器的结果。 +三道门各管一件事,红了代表不同的意思: -## 4. Run +- `doctor` 红 = **环境问题**:缺二进制、pin 对不上、adapter 没编译。 +- `validate` 红 = **任务集对不上**:磁盘上的任务集和 manifest 不一致。 +- 单测挂 = **框架自身坏了**。 -smoke(几分钟): +这三种情况都不是浏览器能力的问题,**别把它们当成被测引擎的成绩**。 + +## 4. 开始运行 + +先跑冒烟测试(smoke,几分钟)确认全链路是通的: ```bash python3 -m runner.run run --subset l1.raw_cdp --tag purpose.smoke \ @@ -84,7 +101,7 @@ python3 -m runner.run run --subset l1.raw_cdp --tag purpose.smoke \ --score-mode independent --chrome-baseline best_effort --seed smoke ``` -完整正式 run(即已发布结果的配置): +再跑完整正式 run(这就是已发布结果所用的配置): ```bash python3 -m runner.run run \ @@ -94,26 +111,27 @@ python3 -m runner.run run \ --run-id --provenance-level minimal ``` -## 5. 一次 run 产出什么 +参数速览:`--k 3` = 每道题尝试三次;`--jobs 16` = 16 路并行跑;`--host-telemetry on` = 开启宿主机资源遥测;`--provenance-level minimal` = 溯源级别只保留硬件事实、去掉部署信息。 -全部落在 `runs//`: +## 5. 跑完会得到什么 + +一次 run 的全部产物都在 `runs//` 下: | 文件 | 内容 | |:---|:---| | `run_manifest.json` | 完整溯源:引擎身份、harness pin、runner 源码树 / fixture 树 / 编译 adapter 的摘要 | -| `results.jsonl` | 每 task × 引擎 × attempt 一行,带状态与失败归因 | -| `scores.json` | 按评测轴聚合的分数 | +| `results.jsonl` | 每道题 × 引擎 × 尝试一行,带状态与失败归因 | +| `scores.json` | 按评测维度聚合后的分数 | | `scorecard.md` | 给人看的摘要 | ## 6. 计分模式 -`--score-mode independent`:每个选中的引擎只按自己的 attempt 计分。这是发布配置;Chrome 以参照列身份参与,不设门。默认的 `baseline_checked` 则启用 Chrome 基线策略、只给候选引擎计分。 - -`--chrome-baseline` 用 `best_effort` 而不是 `required` 的原因:`required` 会把 Chrome 失败的题从所有引擎的分母中剔除,Chrome 自己那列会被构造性地推向 100%,作为对照列就失去了意义。 +- **`--score-mode independent`**:每个选中引擎只按自己的尝试计分。这是发布配置;Chrome 以参照列身份参与、不设门槛。默认的 `baseline_checked` 则启用 Chrome 基线策略,只给候选引擎计分。 +- **`--chrome-baseline` 为什么用 `best_effort` 而不用 `required`**:如果选 `required`,Chrome 挂掉的题会被从所有引擎的计分分母里剔除,等于把 Chrome 自己的成功率"保送"到接近 100%——它作为参照列就失去意义了。 ## 7. 资源测量 -功能分和资源测量不共用一次 run。A/B 协议是同机器、同任务集、同 seed 的两次 run: +功能成绩和资源测量**不共用同一次 run**(功能轮归功能轮,资源轮归资源轮)。A/B 协议 = 同一台机器、同一套题、同一个 seed 跑两轮: ```bash # A 轮:基线,profiler 关 @@ -124,10 +142,10 @@ python3 -m runner.run run ... --resource-profile engine --jobs 1 --k 5 --score-m --resource-calibration-baseline runs/ ``` -B 轮结束时拿自己的任务时长分布和 A 轮对比,量出 profiler 本身对引擎的干扰。CPU、PSS、进程数、fixture 流量只有在干扰过了校准门(`resource_comparison_eligible: true`)时才报告。完整契约见 [resource-cost.zh.md](resource-cost.zh.md)。 +B 轮结束时,拿自己的任务耗时分布和 A 轮对比,量出 **profiler(性能剖析器)本身对引擎的干扰**。CPU、内存(PSS,进程实际占用内存的估算)、进程数、页面流量——这些只有在干扰过了校准门(`resource_comparison_eligible: true`)时才会报告。完整契约见 [resource-cost.zh.md](resource-cost.zh.md)。 ## 常见失败 -- `doctor` 报 pin 不匹配:`build_artifacts/` 下的二进制不是 pin 的那个构建。激活正确的 set,或有意识地更新 `active-set.json`。 -- 结果行大量 `infra`:身份门失败,客户端没有连到它该连的引擎。这是环境或路由问题,永远不是兼容性分数。 -- 编译型 adapter 缺失:用上面的 Go/Rust 命令重新编译;`doctor` 会打印它期望的确切命令。 +- **`doctor` 报 pin 不匹配**:`build_artifacts/` 下的二进制不是你 pin 的那个构建。激活正确的 set,或者有意识地更新 `active-set.json`。 +- **结果行大量 `infra`**:身份门没过——客户端没连到它该连的引擎。这是环境或路由问题,**永远不是**兼容性分数。 +- **编译型 adapter 缺失**:用上面的 Go/Rust 命令重新编译;`doctor` 会打印它期待的确切命令。 diff --git a/docs/resource-cost.zh.md b/docs/resource-cost.zh.md index e2407cf..55ffc25 100644 --- a/docs/resource-cost.zh.md +++ b/docs/resource-cost.zh.md @@ -1,17 +1,14 @@ -# Resource cost 与公平对比契约 +# 资源开销 [English](resource-cost.md) · [中文](resource-cost.zh.md) -Resource telemetry 是功能分之外的独立观测维度。它回答“同一批成功任务消耗了多少 -CPU、内存和 fixture 应用流量”,不改变 task 的 pass/fail,不并入 native capability -分,也不生成混合“资源总分”。 +**这一页在讲什么:** 资源遥测是功能分之外的一条**独立观测维度**。它回答"同一批成功任务,各引擎消耗了多少 CPU、内存和页面流量",但**不改变** task 的 pass/fail、不并入原生能力分、也不生成什么"资源总分"。 -当前支持 `chrome`、`moli`、`lightpanda`、`obscura`;聚合维度从 -`run_manifest.selected_engines` 解析,不再硬编码三引擎。 +目前支持 `chrome`、`moli`、`lightpanda`、`obscura` 四个引擎;聚合维度从 `run_manifest.selected_engines` 解析,不再硬编码三引擎。 ## 两种运行形态 -普通 `run` 默认开启低频 host telemetry: +**普通 `run`** 默认开启低频采样: ```bash python3 -m runner.run run \ @@ -20,16 +17,12 @@ python3 -m runner.run run \ --host-sample-interval-s 2 ``` -它记录 load、MemAvailable、swap、CPU/memory/IO PSI、benchmark 后代进程数、 -kernel、CPU governor 与 cgroup 版本。污染门会标记 swap 活动、低可用内存、 -持续 PSI 或异常进程增长,但不会改写功能结果。确有需要时可以用 -`--host-telemetry off` 关闭。 +它记录:load、MemAvailable、swap、CPU/内存/IO 的 PSI(资源压力停滞指标)、benchmark 后代进程数、kernel、CPU governor 与 cgroup 版本。同时会标记 swap 活动、低可用内存、持续 PSI 或异常进程增长——但**只做标记,不改写功能结果**。确有需要时可以用 `--host-telemetry off` 关掉。 -Engine profile 是显式 opt-in。正式资源对比应对同一 corpus、seed、k 和机器先跑 -profiler-off 基线,再跑 profiler-on: +**Engine profile(引擎级资源剖析)是显式主动开启的。** 正式做资源对比时,应当用同一批任务集、seed、k 和同一台机器,先跑 profiler-off 基线,再跑 profiler-on: ```bash -# A:平衡引擎顺序,但不启用 attempt profiler +# A 轮:平衡引擎顺序,但不启用 attempt profiler python3 -m runner.run run \ --subset l1.raw_cdp \ --tag purpose.smoke \ @@ -41,7 +34,7 @@ python3 -m runner.run run \ --resource-profile baseline \ --run-id resource_ab_off -# B:同一矩阵开启 CPU/PSS/fixture traffic +# B 轮:同一矩阵,开启 CPU / PSS / fixture traffic 测量 python3 -m runner.run run \ --subset l1.raw_cdp \ --tag purpose.smoke \ @@ -57,95 +50,73 @@ python3 -m runner.run run \ --run-id resource_ab_on ``` -`baseline` 与 `engine` 都按连续 task-attempt 做严格循环轮换;任意 N 个 attempt -中,每个引擎占据各顺序位的次数最多相差 1,seed 只决定第一轮的偏移。 -短任务上扫描完整 Chrome 进程树本身可能很贵,因此 500ms 是保守起点;应根据 A/B -结果调整,而不是机械追求更高采样频率。 +`baseline` 与 `engine` 两种模式都按连续 task-attempt 做**严格循环轮换**(轮流分配位置):任意 N 个 attempt 里,每个引擎占据的各个顺序位的次数最多相差 1;seed 只决定第一轮的偏移。完整扫描 Chrome 的进程树在短任务上本身可能很贵,所以 500 ms 是保守起点——应根据 A/B 结果调整,而不是机械地追求更高采样频率。 -## Scope 与指标 +## 度量的作用域与指标 三个 scope 不混算: -- `engine_scope`:engine root 及全部 descendants,是所选引擎资源对比的主对象。 -- `harness_scope`:driver、adapter、grader 等控制成本;不计入 engine 侧的主对比。 -- `host_scope`:整机环境与污染证据,只用于可比性和归因。 +- `engine_scope`:engine 根进程及其全部后代进程——所选引擎资源对比的**主对象**。 +- `harness_scope`:driver、adapter、grader 等控制成本;**不计入**引擎侧的主对比。 +- `host_scope`:整机环境与污染证据;只用于可比性判断和归因。 -Warm attempt 至少记录: +每个热启动 attempt 至少记录: -- CPU:`cpu_total_ms`、`cpu_user_ms`、`cpu_system_ms`、`avg_cores`。 -- PSS:`pss_baseline_bytes`、`pss_peak_bytes`、`pss_end_bytes`、 - `pss_peak_delta_bytes`。 -- 进程:baseline/peak/end process count,以及 run 级 PSS/process leak slope。 -- cgroup memory:current/peak 作为补充 accounting;不会冒充 PSS。 -- 流量:fixture application request/response headers 和 body bytes。 +- **CPU**:`cpu_total_ms`、`cpu_user_ms`、`cpu_system_ms`、`avg_cores`。 +- **PSS**(比例集大小,进程实际占用内存的合理估算):`pss_baseline_bytes`、`pss_peak_bytes`、`pss_end_bytes`、`pss_peak_delta_bytes`。 +- **进程**:baseline/peak/end 三个时点的进程数,以及 run 级 PSS/进程泄漏斜率。 +- **cgroup 内存**:current/peak 作为补充统计口径上传——它不会冒充 PSS。 +- **流量**:fixture 服务端收到/发出的请求响应 header 与 body 字节数。 -Cold start 单独写入 `cold_start.jsonl`,记录 `ready_ms`、`launch_cpu_ms` 和 -`launch_peak_pss_bytes`,不进入 warm 聚合。 +冷启动单独写入 `cold_start.jsonl`,记录 `ready_ms`、`launch_cpu_ms`、`launch_peak_pss_bytes`,**不进入** warm 聚合。 -## Backend 与失败语义 +## 底层实现与失败语义 -CPU 优先使用每个 worker×engine 独立的 cgroup v2 `cpu.stat`。没有委派权限时, -fallback 为 `/proc/PID/stat` 的完整进程树累计,并写 -`proc_tree_child_exit_loss_risk`。 - -PSS 使用每个进程的 `/proc/PID/smaps_rollup`,按完整后代树求和。`/proc` 无法提供 -原子 tree+PSS 快照,因此扫描会对短暂的子进程退出做有界重试。已确认的 zombie -仍计入 process count,但其已释放的地址空间按 0 live PSS 处理,并写 -`pss_zero_address_space_process`。权限错误或无法确认的读取失败会写 -`unavailable.pss`,绝不写成 0。有独立 cgroup 时从 `cgroup.procs` 枚举完整集合, -无 cgroup 时遍历 `/proc/PID/task/TID/children`;PSS 文件并行读取以压低 observer -wall overhead,同时把各 reader thread 的 CPU 计入 `sampler_cpu_ms`。 -`pss_peak_bytes` 是该 attempt 所有 PSS samples 的最大值;短任务若只有 -baseline/end 两点会显式写 `baseline_end_only`,不能把它解释成连续采样的瞬时峰值。 - -资源采集异常被隔离在 profiler 内:task 的功能状态仍由原 driver/grader 决定。 +- **CPU**:优先使用每个 worker × engine 独立的 cgroup v2 `cpu.stat`;没有委派权限时,退回 `/proc/PID/stat` 的完整进程树累计,并写 `proc_tree_child_exit_loss_risk` 标记丢失风险。 +- **PSS**:对每个进程读 `/proc/PID/smaps_rollup`,按完整后代树求和。`/proc` 无法提供原子的"树 + PSS"快照,所以扫描会对短暂的子进程退出做**有界重试**。 +- **僵尸进程**:已确认的 zombie 仍计入进程数,但它已释放的地址空间按 0 live PSS 处理,并写 `pss_zero_address_space_process`。 +- **读失败**:权限错误或无法确认的读取失败,写 `unavailable.pss`,**绝不写成 0**。 +- **枚举方式**:有独立 cgroup 时从 `cgroup.procs` 枚举完整进程集合;没有时遍历 `/proc/PID/task/TID/children`。 +- **并行读取**:PSS 文件并行读取以压低观测墙钟开销,同时把各读取线程的 CPU 计入 `sampler_cpu_ms`。 +- **峰值语义**:`pss_peak_bytes` 是该 attempt 所有 PSS 样本的最大值;短任务如果只有 baseline/end 两个点,会显式写 `baseline_end_only`——不能把它解释成连续采样的瞬时峰值。 +- **异常隔离**:资源采集的异常被隔离在 profiler 内——task 的功能状态仍由原 driver/grader 决定,不受影响。 ## Fixture 流量的统计范围 -Phase 1 的 portable 真值来自自托管 fixture server: +Phase 1 的可移植后端真值来自自托管的 fixture server: -- `fixture_app_rx_body_bytes`:server 收到的 HTTP body 与 WebSocket client payload。 -- `fixture_app_tx_body_bytes`:server 发出的 HTTP/SSE body 与 WebSocket server - payload。 +- `fixture_app_rx_body_bytes`:服务器收到的 HTTP body 与 WebSocket 客户端 payload。 +- `fixture_app_tx_body_bytes`:服务器发出的 HTTP/SSE body 与 WebSocket 服务端 payload。 - `fixture_app_rx_header_bytes` / `fixture_app_tx_header_bytes`:HTTP 应用层 headers。 -- redirect、HTTP upload/download、SSE、WebSocket 都走同一个计数器。 -- `/__grade__/` 与 `/__event__/` 进入单独的 harness 计数,不混入 fixture app。 +- 重定向、HTTP 上传/下载、SSE、WebSocket 都走**同一个计数器**。 +- `/__grade__/` 与 `/__event__/` 进入单独的 harness 计数,**不混入** fixture app。 -`jobs=1` 时 active attempt 唯一,归属无歧义。并发 diagnostic run 会尝试通过 -session/referrer/cookie/body 归属;无法唯一归属时标记 unavailable,绝不伪造为 -0。portable backend 暂不实现 control-plane bytes 和 wire bytes,报告会带明确 -reason。 +`jobs=1` 时 active attempt 唯一,归属没有歧义。并发诊断 run 会尝试通过 session/referrer/cookie/body 做归属;无法唯一归属时标记 `unavailable`,**绝不伪造为 0**。portable backend 暂不实现 control-plane bytes 和 wire bytes,报告会带明确的 reason。 ## 比较资格 -`resource_comparison_eligible=true` 必须同时满足: +`resource_comparison_eligible=true` 必须**同时满足**以下全部条件: -1. 至少选择两个 engine,且每个 result row 的 engine 都属于同一份 - `selected_engines`; -2. `--resource-profile engine`、`--jobs 1`、`--k >= 5`; -3. `--score-mode independent`,且使用平衡轮换顺序; -4. 所选全部引擎在同一个 `task_id × attempt` 都 pass 的交集非空; -5. 交集所需 CPU/PSS/traffic 指标无缺失; -6. host pollution gate 通过; -7. profiler on/off A/B 有完整配对,corpus/seed/k/jobs/engine+harness pin/host - provenance 完全一致,状态不一致率不超过 1%,中位 task duration 与包含 - baseline/end 扫描在内的 collection-wall 开销在整体和每个引擎上都不超过 - 配置阈值。 +1. 至少选择两个引擎,且每条结果行的引擎都属于同一份 `selected_engines`; +2. 使用 `--resource-profile engine`、`--jobs 1`、`--k >= 5`; +3. 使用 `--score-mode independent`,且采用平衡轮换顺序; +4. 所选全部引擎在同一个 `task_id × attempt` 都 pass 的交集**非空**; +5. 交集所需的 CPU/PSS/traffic 各指标没有任何缺失; +6. host 污染门通过; +7. profiler on/off 的 A/B 完整配对:corpus、seed、k、jobs、引擎+harness pin、host provenance 完全一致;状态不一致率不超过 1%;中位任务时长与包含 baseline/end 扫描在内的采集墙钟开销,在整体和每个引擎上都不超过配置阈值。 -报告只聚合 all-pass intersection。fail/unsupported/timeout 仍保留原始资源数据, -并在 `excluded.status_by_engine` 中列出,避免把 fail-fast 当成效率优势。每个指标 -输出样本数、median、p95 和 bootstrap median 95% CI。 +报告只聚合"全部通过的交集"。fail/unsupported/timeout 仍保留原始资源数据,并在 `excluded.status_by_engine` 里列出——避免把"快速失败"误当成效率优势。每个指标都输出**样本数、median、p95 和 bootstrap median 95% CI**。 ## 产物 -普通 run 增加: +普通 run 额外产生: ```text host_telemetry.jsonl host_summary.json ``` -`--resource-profile engine` 另外增加: +`--resource-profile engine` 模式下另外增加: ```text cold_start.jsonl @@ -154,15 +125,12 @@ resource-card.md artifacts//////resource.jsonl ``` -Attempt summary 位于 `results.jsonl[].resource`。backend、采样间隔、样本数、 -sampler CPU、quality flags、host/kernel/governor、jobs、reuse、engine SHA 和 -harness pin 都进入 run provenance;`runner.source.tree_sha256` 对有效 runner 与 -adapter 源文件的实际内容求 hash,包含未提交修改。当前 schema 为: +Attempt 摘要位于 `results.jsonl[].resource`。backend、采样间隔、样本数、sampler CPU、质量标记(quality flags)、host/kernel/governor、jobs、reuse、引擎 SHA 和 harness pin 都进入 run 溯源;`runner.source.tree_sha256` 对有效 runner 与 adapter 源文件的**实际内容**求 hash(包含未提交的修改)。 + +当前 schema 为: - `abb_host_telemetry/1` - `abb_engine_resources/1` - `abb_fixture_traffic/1` - `abb_resource_summary/1` - `abb_runner_source/1` - -