Skip to content

perf: 热路径与首屏优化(lsof 超时收敛、bots-info 指纹缓存、Dashboard 首屏 −20%) - #1850

Merged
deepcoldy merged 3 commits into
masterfrom
perf/hot-path-quick-wins
Oct 13, 2026
Merged

deepcoldy merged 3 commits into
masterfrom
perf/hot-path-quick-wins

Conversation

@deepcoldy

Copy link
Copy Markdown
Owner

改了什么

三处热路径 / 首屏优化,分 3 个提交:

  1. perf(transcript):按 pid 枚举打开文件的 6 份实现收敛,lsof 统一加超时
    • codex / coco / cursor / grok / pi / traex 六个 transcript 服务各自实现了"/proc/<pid>/fd 快路径 + lsof 兜底",其中 5 份用的是无 timeout 的 execSync('lsof -p …')。session-discovery 的 readCwd 同样如此。
    • macOS 上 lsof 一旦卡住(慢挂载、对 socket 做 DNS / 端口名反查),worker / daemon 事件循环会被同步阻塞。
    • 新增 utils/process-open-files.ts,lsof 走 execFileSync(无 shell),设 2s 超时 + SIGKILL、4MB maxBuffer,并加 -n -P 跳过名称解析。只取 n/ 文件路径行,结果不变。
    • coco / grok 保留跳过 procfs (deleted) 的语义。
    • 新增源码锁,禁止 services / core 再出现无界 execSync(\lsof`。已验证在 master 上能拦下全部 6 处。
  2. perf(lark):热路径上的 bots-info.json 读取改为按文件指纹缓存
    • 每个 turn 的 final output 路径(2–3 次)与每条带 @ 的入站消息,都要整文件 readFileSync + JSON.parse。
    • 新增 services/bots-info-cache.ts,沿用 peer-cross-ref-store 的 stat 指纹方案(dev:ino:size:mtime:ctime):每次仍 stat,兄弟 daemon 原子替换后立即可见。
    • 语义与原 existsSync + JSON.parse 逐项等价:stat 失败 → undefined;文件损坏 → 每次抛同一个解析错误,调用方原有 catch 分支不变。
    • 写路径与 fail-closed 读取方保持直读。
  3. perf(dashboard):抽出共享表单控件,bot-defaults 页回到懒加载
    • 被 app.tsx 静态引入的 bot-onboarding.tsx 为了 ModelPickerField 直接 import 了 8k 行的 bot-defaults-page.tsx,使这个路由级懒加载页被打进首屏。
    • 两个控件原样移到 field-controls.tsx。
    • 首屏 JS(app.js 静态可达的全部 chunk,minify 后)从 1117KB 降到 888KB,gzip 从 300KB 降到 249KB。

影响面

  • lsof:
    • 影响所有走 pid → transcript 反查的 CLI(codex、coco、cursor、grok、pi / oh-my-pi、traex)的 adopt / 恢复路径,以及 session-discovery。
    • Linux 主路径仍是 procfs,只有 procfs 不可读时才回落 lsof。
    • macOS 上行为变化仅在 lsof 超过 2s 时出现:此前会一直阻塞,现在按"无法枚举"处理,调用方本就有这个分支。
    • traex 原先在 /proc 缺失时直接返回 undefined,现与其余五份一致回落 lsof;pid 已死时 lsof 同样失败,结果不变。
  • bots-info 缓存:只接在 worker-pool 的 loadKnownBotOpenIdsForApp 和 event-dispatcher 的兄弟 bot 名解析两个热点上,其余读取方未动。
  • Dashboard:纯拆包,组件代码原样搬移,无视觉变化。

验证

  • npx tsc --noEmit -p tsconfig.json:零错误。
  • 新增 test/process-open-files.test.ts,覆盖:真实打开的文件可枚举、卡死的 lsof 在上界内失败、Linux 下 deleted 过滤、源码锁。
  • 新增 test/bots-info-cache.test.ts,覆盖:同快照复用、兄弟 daemon 原子替换可见、文件消失可见、损坏时每次抛错且修复后恢复。
  • macOS 真实子进程 smoke codex-coco-pid-discovery.smoke.test.ts 通过,lsof 相关 11 个测试文件 428 条通过。
  • write-input.test.ts 的 node:child_process mock 中 execFileSync 原为返回 undefined 的空桩,改为与 execSync 一致返回 ''。
  • 涉及 bots-info 的 20 个测试文件 1504 条通过。Dashboard 相关 32 个测试文件 995 条通过(本机需把 react alias 到同一副本)。
  • 全量 vitest --project unit 与未改动的 master 按用例做差集,零新增失败。与另外 4 个分支合并后的整树 bun run build 通过。
  • 首屏体积:用 esbuild(splitting + minify)分别打包 master 与本分支的 app.tsx,统计 app.js 静态可达的 chunk 总和。

codex / coco / cursor / grok / pi / traex 六个 transcript 服务各自实现了一份
"/proc/<pid>/fd 快路径 + lsof 兜底",其中 5 份用的是无 timeout 的
`execSync('lsof -p …')`:macOS 上 lsof 一旦卡住(慢挂载、对 socket 做 DNS /
端口名反查),worker / daemon 的事件循环会被同步阻塞。session-discovery 的
readCwd 同样如此。

- 新增 utils/process-open-files.ts:listProcessOpenFiles(pid, { skipDeleted })
  统一 Linux procfs 快路径与 lsof 兜底;lsof 走 execFileSync(无 shell)、
  2s 超时 + SIGKILL、4MB maxBuffer(execSync 默认 1MB),并加 `-n -P` 跳过
  主机名/端口名解析——只消费 `n/` 文件路径,结果不变
- 六个调用方改为"枚举 + 各自匹配器",coco / grok 保留跳过 procfs
  `(deleted)` 的语义;traex 原先在 /proc 缺失时直接返回 undefined,现与其余
  五份一致回落 lsof(pid 已死时 lsof 同样失败,结果不变)
- session-discovery readCwd 的 lsof 复用同一组有界选项
- 测试:新增 process-open-files 单测(真实打开文件可枚举、卡死的 lsof 在
  上界内失败、Linux deleted 过滤),并加源码锁禁止 services/core 再出现无界
  `execSync(\`lsof`(已验证在 master 上能拦下全部 6 处);traex 原有的内联
  实现源码锁随之迁入。macOS 真实子进程 smoke(codex-coco-pid-discovery)通过
bots-info.json 由每个 bot daemon 合并写入,内容只在 daemon 启动/改名时变化,
但以下热路径每次都 existsSync + readFileSync + JSON.parse 整个文件:
- worker-pool loadKnownBotOpenIdsForApp:每个 turn 的 final output 路径上
  调用 2–3 次(页脚收件人、失败兜底 @、零注入发起人)
- event-dispatcher 的兄弟 bot 名解析与 updateBotOpenIdCrossRef:每条带 @
  的入站消息

新增 services/bots-info-cache.ts,沿用 peer-cross-ref-store 的指纹方案
(dev:ino:size:mtime:ctime):每次仍 stat,只有兄弟 daemon 原子替换后才重读
重解析。语义与原先 existsSync + JSON.parse 逐项等价:stat 失败 → undefined;
损坏 → 每次抛同一个解析错误(按指纹缓存),调用方原有 catch 分支行为不变。
写路径(writeBotInfoFile 的读-改-写)与 fail-closed 读取方保持直读。
bot-onboarding.tsx(被 app.tsx 静态引入)与 group-default-models.tsx 为了
ModelPickerField / DropdownField 直接 import 8k 行的 bot-defaults-page.tsx,
使这个本应按路由懒加载的整页被打进首屏静态依赖。

- 两个控件(及 DropdownFieldOption 类型)原样移到新的 field-controls.tsx,
  三个使用方改从这里引入;dashboard-routes 的懒加载入口成为 bot-defaults-page
  唯一的引用方
- 测试:model-picker 源码锁改为断言 onboarding 从 field-controls 引入且不再
  静态依赖 bot-defaults-page;group-default-models 测试改 import 路径

效果(esbuild splitting + minify,统计 app.js 静态可达的全部 chunk):
首屏 JS 1117KB → 888KB(gzip 300KB → 249KB),bot-defaults-page 不再在首屏。
@deepcoldy
deepcoldy merged commit 656fd68 into master Oct 13, 2026
17 checks passed
@github-actions

Copy link
Copy Markdown

🚀 Released in v3.43.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant