feat(bash): 在终端标题栏展示最近一次 LLM call 输出速度 (tok/s) - #87
Conversation
- claude_sse.awk 在 transport 层记录 start_ms/end_ms - USAGE 事件下沉时间字段,上层用 output_tokens/duration 计算速度 - stats.json 新增 last_call_speed_tok_per_sec - term_title.awk 标题栏增加 S:<speed>tok/s - 删除未使用的 util_date_ms - 更新 tests/test.sh async bash 标题栏 golden Tested: make test-bash (221 passed)
代码 review 意见总体评价实现简洁,把耗时记录下沉到 transport 层是对的;测试已全绿(221/0)。 逐文件检查
|
- transport 层记录 SSE 流 start/end 毫秒时间戳(读流开始→流结束,不含建连/TTFB) - agent 层 speed = output_tokens / duration_ms * 1000,duration>0 除零守卫 - stats.json 写入 last_call_speed_tok_per_sec,终端标题栏展示 S:xx tok/s - compact/sub_agent 的 usage 不更新 speed,口径与 bash 版一致 - tests/test.sh Test 40 增加 speed 字段检查 - 三版本 e2e 均 222 passed
原 date_ms 三级 fallback 在 macOS 上两级是坏的: - date +%s%3N: BSD date 不认 %N,返回垃圾值 - systime(): macOS bwk awk 无此函数,直接崩溃 - 实际全靠系统 perl 兜底 新方案: - curl -w 在 SSE 流末尾追加标准 event:timing 块(time_total/time_starttransfer) - 块沿现有管道自然流转,openai/responses 转换层各加 2 行透传规则 - claude_sse.awk END 里直接算:speed = out / (total - ttfb) 口径对齐 Go/Rust/C:读流开始→流结束,排除建连/TLS/TTFB - USAGE 第 5 字段携带算好的 speed,bash 侧零计算 - --retry 时 -w 只在最终成功传输输出一次,speed 不含重试等待 - 零 fallback、零外部进程、零新依赖、毫秒精度
Follow-up: Bash 版 speed 计时重构(f3ff4a8)问题:
|
| 分支 | macOS 实际行为 |
|---|---|
perl -MTime::HiRes |
✅ 正常(当前实际走的路径) |
date +%s%3N |
❌ BSD date 不认 %N,输出垃圾值 "17890412273N"(秒×10+3) |
systime() |
❌ gawk 扩展,bwk awk 无此函数,calling undefined function 直接崩溃 |
即 fallback 能工作全靠系统自带 perl,且 --retry 等待时间被计入 duration。
新方案:curl -w 构造标准 SSE timing 块
curl -w '\nevent: timing\ndata: {"time_total":%{time_total},"time_starttransfer":%{time_starttransfer}}\n\n'
-w在 SSE 流末尾追加标准event: timing块,沿现有管道自然流转:
http_stream.awk(body 全量穿透,零改动)→sse_convert(openai/responses 各加 2 行透传规则)→claude_sse.awkclaude_sse.awk新增event == "timing"分支捕获,END 里 awk 浮点直接算好:
speed = output_tokens / (time_total - time_starttransfer)- USAGE 第 5 字段携带算好的 speed,bash 侧零计算
- 口径对齐 Go/Rust/C:读流开始→流结束,排除建连/TLS/TTFB(Go 的
startMs在bufio.NewScanner(resp.Body)前,Rust 在read_sse前,语义等价于total - ttfb) - 实测确认
--retry时-w只在最终成功传输输出一次:speed 不含失败重试的等待时间 - 零 fallback、零外部进程、零新依赖(
-w是 curl 远古特性)、毫秒精度
gen > 0 守卫仅为除零防御(响应极小时 total == ttfb),与原版 _dur > 0 一脉相承。
测试
- awk 单元验证:Claude 路径
50/(10-3)=7✓、OpenAI 全管道透传 ✓、gen=0 → speed=0✓ - 真实 curl 输出格式验证(
\n转义 → 标准 SSE 块)✓ make test-bash: 222 passed, 0 failed(与 Go/Rust/C e2e 测试数一致)
跨 provider review 发现的口径差异(bash 在 message_stop/[DONE]/
response.completed 才发 USAGE,失败终态/中断不发 → speed 保留旧值):
- Go responses 失败终态(response.failed/incomplete/error)也发
USAGE → speed 被部分输出的偏低值覆盖;Rust 同路径同问题;
C 中断场景(收到 message_start 的 in 但未到 message_stop)
流中 USAGE 使守卫通过 → 写 speed=0
- 修复:三版本统一"正常终结"标志
- Go: Usage.Stopped(message_stop/[DONE]/completed=true,
failed=false),store.RecordUsage 仅 Stopped 时写 speed
- Rust: UsageEvent.stopped(同上四处置值),agent 仅
last_stopped 时写 speed
- C: accum->stopped(SSE_STOP 置位)守卫 speed 写入
C 版计时口径修复(对齐 bash 传输时间):
- 原 start_ms 在 curl_easy_perform 前打点,含 DNS/TCP/TLS/TTFB
- 改为 perform 成功后 curl_easy_getinfo 查
CURLINFO_TOTAL_TIME_T / CURLINFO_STARTTRANSFER_TIME_T,
与 bash 的 curl -w 同一套计时器(time_total - time_starttransfer,
仅传输时间);补发最终 USAGE 覆盖流中 now_ms 口径时间戳
(token 字段为 0,>0 守卫不重复累加)
C 版 speed 恒 0 的根因修复:
- agent 层 stream_display_callback 的 SSE_USAGE case 漏记
start_ms/end_ms(transport 层另一份 accumulator 有记,agent
层读自己这份)→ dur 恒 0
亚毫秒截断(三版本):整数 ms dur 截断为 0 → speed=0 偶发;
传输真实发生时向上取整至少 1ms(对齐 bash awk 浮点行为)
tests/test.sh Test 40 补 S5 断言 speed > 0(原只查字段存在,
C 版恒 0 未被发现的盲区)
四版本 e2e 各 223 passed
Code Review: last-call speed (tok/s) 全链路口径矩阵(已验证一致)speed 分母统一为「传输时间」=流式 body 传输全程,排除 DNS/TCP/TLS/TTFB:
Review 中发现并已修复的问题
遗留差异(token 记账层面,建议 follow-up,不影响 speed)USAGE 发出时机架构不同(bash=解析器 END 汇总一次;Go/Rust/C=流中逐事件)导致:
两者均为统计口径的轻微偏差,如需严格对齐需把 token 累加也纳入「正常终结」守卫,或把 port 版 USAGE 改为终结时汇总发出。 测试基建发现mock server 的 responses 分支挂在 验证
|
debian:11 (bullseye) 的 bullseye-security InRelease 元数据过期 (invalid since 2d),导致所有 Debian 11 container 构建 job 的 apt-get update 退出 100。加 -o Acquire::Check-Valid-Until=false 跳过检查(与代码改动无关的 CI 基建问题)
bullseye LTS 已于 2026-08-31 结束,deb.debian.org 上的 bullseye-security 旧版包已清理(install 阶段 404)。 sed 替换占位行改为指向 archive.debian.org(EOL 官方归档, 包永久可用),配合已有的 Check-Valid-Until=false
bullseye 已于 2026-08-31 结束 LTS,镜像进入元数据过期+pool 清理 的半死状态。切换 bookworm(支持至 2026+6/2028 LTS)。⚠️ 兼容性变化:Linux 构建产物 glibc 下限从 2.31 升至 2.36, 不再支持 Ubuntu 22.04 (2.35) 及更早;Ubuntu 24.04+/Debian 12+ 不受影响。 同时清理 bullseye EOL 的临时 workaround(archive/snapshot sed 替换与 Check-Valid-Until=false,debian:12 活仓库无需)
替代 debian:11(bullseye 已 EOL,镜像进入元数据过期+pool 清理 半死状态)和 debian:12(glibc 2.36 会放弃 Ubuntu 22.04 等系统, 违背 v4.3.1 的 2.31 基线承诺)。 ubuntu:20.04 (focal) glibc 同为 2.31: - 门禁 GLIBC_MIN=2.31 / deb 声明 libc6 (>= 2.31) / README 承诺全部不变 - focal 仍在 archive.ubuntu.com 正常服务(主仓/security 均 200), 且 Ubuntu Release 无 Valid-Until 字段,不存在 Debian 的过期检查问题 - 产物兼容性完全等价:Debian 11/12/13、Ubuntu 20.04/22.04/24.04+
审查结论:暂不建议合并审查范围: 1. 中:Go 与其他版本的计时终点不一致位置: Go 在收到协议结束事件时记录结束时间;Bash、C、Rust 的 Claude 路径则等待响应流结束。结束事件与连接关闭之间存在延迟时,同一响应会得到明显不同的速度。 已使用本地模拟服务串行实测:输出 100 个词元,约 0.25 秒后发送结束事件,再等待 2 秒关闭连接。四版本均成功退出,用量一致,持久化速度分别为:
这不是舍入误差,违反四版本行为一致性的要求。建议统一计时边界;若改用协议结束事件,也需要同步其他版本,并补充可控时延的数值断言。 2. 中:C 中断时可能覆盖上次成功调用的速度位置: 新增写入逻辑仅检查
例如,Claude 已返回输入用量、尚未返回输出用量时中断,可能将旧速度覆盖为零,而不是保留上次成功值。 此项为代码路径确认,尚未实际执行交互中断复现。建议明确区分正常完成和中断,再决定是否更新速度,并补充“成功后失败/中断保留旧值”的回归测试。 测试与构建说明
已完成的验证及边界
建议修复上述两项行为问题、补齐对应回归测试后再合并。 |
变更
claude_sse.awk)记录 LLM 响应的开始/结束毫秒时间戳。USAGE事件追加start_ms/end_ms字段,速度计算下沉到 transport 层之后由上层一次性完成。stats.json新增last_call_speed_tok_per_sec字段。term_title.awk标题栏增加S:<speed>tok/s展示。util_date_ms。tests/test.shasync bash 标题栏 golden。测试
make test-bash:221 passed / 0 failed。