跳转至

RUNBOOK:工具执行埋点(tool-timing)

读者:接手本机 Trae 环境的人、事后分析时间去向的人。 实现体:tool-timing-hook/tool-timing.py|本次变更档案:tool-timing-20261007/SOP.md

1. 服务概览

项 内容
是什么 一个 Hook 脚本,无守护进程、无端口、无常驻内存
干什么 每次工具调用前后各跑一次:Pre 记开始时刻,Post 算时长并追加一行日志
由谁触发 Trae 客户端的 PreToolUse 与 PostToolUse(hooks.json 中 matcher: "*" 各一条)
依赖 python3(系统自带)、~/.trae-cn/hooks.json、可写的 logs/ 与脚本目录
产出 logs/tool_timing.jsonl(append-only)、tool-timing-hook/state/(临时态,1 h 回收)
业务影响 无。它失败只损失度量数据,不影响代理服务、不影响工具调用本身
重要性 低(观测类)。可作为「时间分析」的唯一工具侧数据源,但不可用于生产决策链路

它解决什么:此前只有 logs/usage.jsonl(ts + token),步间隔 = 生成 + 工具执行 + 往返,三者不可分; 本埋点把工具执行时长单独量出来。

2. 路径与位置

用途 路径
实现体 ~/Documents/services/token_proxy/tool-timing-hook/tool-timing.py
数据日志 ~/Documents/services/token_proxy/logs/tool_timing.jsonl
临时状态 ~/Documents/services/token_proxy/tool-timing-hook/state/(k-* 开始时刻、d-* 完成墓碑、prune.log 删除留痕)
挂载配置 ~/.trae-cn/hooks.json(PreToolUse / PostToolUse 各一条 matcher: "*")
离线自测 ~/Documents/services/token_proxy/tool-timing-20261007/verify_tool_timing.py

无凭据:本组件不连任何服务、不持有任何口令。

3. 日常速查表

# ① 最近 5 条埋点(确认在跑)
tail -n 5 ~/Documents/services/token_proxy/logs/tool_timing.jsonl

# ② 总行数(累计工具调用数)
wc -l ~/Documents/services/token_proxy/logs/tool_timing.jsonl

# ③ 漏挂检测:miss_pre 为真的条数(应为 0)
grep -c '"miss_pre": true' ~/Documents/services/token_proxy/logs/tool_timing.jsonl

# ④ 按工具聚合耗时(中位 / p90 / 合计占比)
cd ~/Documents/services/token_proxy && python3 - <<'PY'
import json, statistics as st
from collections import defaultdict
g = defaultdict(list)
for ln in open("logs/tool_timing.jsonl", encoding="utf-8"):
    try:
        d = json.loads(ln)
    except ValueError:
        continue
    if isinstance(d.get("dur_ms"), (int, float)):
        g[d.get("tool") or "?"].append(d["dur_ms"])
tot = sum(sum(v) for v in g.values()) or 1
for k, v in sorted(g.items(), key=lambda kv: -sum(kv[1])):
    v = sorted(v)
    print("%-16s n=%-6d 中位=%9.0fms p90=%9.0fms 合计=%8.2fmin 占=%5.1f%%"
          % (k, len(v), st.median(v), v[min(len(v) - 1, int(len(v) * 0.9))], sum(v) / 60000, 100 * sum(v) / tot))
PY

# ⑤ 挂载是否完整(预期输出 2)
python3 -c "import json,os;d=json.load(open(os.path.expanduser('~/.trae-cn/hooks.json')));print(sum(1 for ev in ('PreToolUse','PostToolUse') for it in d['hooks'][ev] for h in it['hooks'] if 'tool-timing' in h['command']))"

4. 日常操作

启用 / 停用:本组件由 hooks.json 挂载控制,没有开关文件。

# 停用(两条一起摘除;临时停用可只摘 PostToolUse——Pre 仍写 state,由 1 h 回收清理)
python3 - <<'PY'
import json, os
P = os.path.expanduser("~/.trae-cn/hooks.json")
d = json.load(open(P, encoding="utf-8"))
for ev in ("PreToolUse", "PostToolUse"):
    d["hooks"][ev] = [it for it in d["hooks"][ev]
                      if not any("tool-timing.py" in h.get("command", "") for h in it.get("hooks", []))]
json.dump(d, open(P, "w", encoding="utf-8"), ensure_ascii=False, indent=1)
print("已摘除")
PY

# 启用:见 SOP.md 第 5 章步骤 2 的幂等挂载脚本(重复执行安全)

两种操作均即时生效,无需重启(2026-10-07 实测:改 hooks.json 后下一次工具调用即落盘); 若观察到不生效,再重启 Trae 兜底。

清空状态目录(不影响日志;高危删除,先确认路径):

ls -1 ~/Documents/services/token_proxy/tool-timing-hook/state/     # 先看
find ~/Documents/services/token_proxy/tool-timing-hook/state/ -type f -name '[kdt]-*' -delete   # 只删本组件产物

5. 配置项(脚本内常量)

常量 现值 含义与调整时机
STALE_SECONDS 3600 state / 墓碑文件的保留期。调小 → state 目录更干净,但「Pre 到了 Post 没到」的错配窗口变短
STATE_PREFIX / TMP_PREFIX / DONE_PREFIX k- / t- / d- 状态文件名前缀,用于回收时区分「本组件产物」与其它文件
LOG_FILE <脚本目录>/../logs/tool_timing.jsonl 由脚本位置推导,移动脚本会同时改变日志落点

修改后无需重启 Trae(脚本每次调用重新加载;hooks.json 才需要重启)。

6. 备份与恢复

  • 挂载配置的备份:tool-timing-20261007/backup/home/rain/.trae-cn/hooks.json.bak.20261007-071701;
  • 数据日志没有自动备份——它是 append-only 且不可再生(与 usage.jsonl 同级), 需要留档时手工 cp: bash cp -p ~/Documents/services/token_proxy/logs/tool_timing.jsonl ~/tool_timing.$(date +%Y%m%d).jsonl
  • 恢复配置:见 SOP.md 第 7 章。

7. 监控与告警

指标 健康值 查看
日志是否在增长 每次工具调用 +1 速查表 ①②
miss_pre 占比 0(>0 说明 Pre 未挂载或 state 被提前清理) 速查表 ③
id_src 分布 应全为 tool_use_id;出现 fallback 说明事件缺 tool_use_id grep -c fallback logs/tool_timing.jsonl
dur_ms 量级 常规工具 10~5000 ms;持续 >30 s 需关注(可能是远端操作或大命令) 速查表 ④
state 目录文件数 应保持在一小时内的调用量级(数十个) ls -1 tool-timing-hook/state/ \| wc -l

8. 故障处置

场景 1:日志完全不增长

症状:触发多次工具调用后 wc -l logs/tool_timing.jsonl 仍不变。

诊断

python3 -c "import json,os;json.load(open(os.path.expanduser('~/.trae-cn/hooks.json')));print('JSON 合法')"   # 预期:JSON 合法
python3 -c "import json,os;d=json.load(open(os.path.expanduser('~/.trae-cn/hooks.json')));print(sum(1 for ev in ('PreToolUse','PostToolUse') for it in d['hooks'][ev] for h in it['hooks'] if 'tool-timing' in h['command']))"   # 预期:2
ls -l ~/Documents/services/token_proxy/tool-timing-hook/tool-timing.py   # 预期:存在

处置:JSON 非法 → 用 SOP 第 7 章方式 A 还原备份;挂载数 <2 → 重跑 SOP 步骤 2; 脚本缺失 → 从版本库/备份恢复。处置后重启 Trae 兜底(正常情况即时生效)。

验证:再触发一次工具调用,tail -n 1 应见新行。

场景 2:miss_pre 大量为真

症状:grep -c '"miss_pre": true' 随时间线性增长。

诊断:说明 Post 在跑而 Pre 没跑。检查 hooks.json 的 PreToolUse 是否被别的工具覆写、 或某次编辑把 PreToolUse 数组结构改坏。

处置:重跑 SOP 步骤 2 的幂等挂载脚本(会补齐缺失的一条);仍不生效再重启 Trae 兜底。

验证:新产生的行 miss_pre 应为 false。历史 true 行不必清理——它们本身就是证据。

场景 3:state 目录膨胀

症状:ls -1 tool-timing-hook/state/ | wc -l 远超一小时的调用量级。

诊断:ls -lt tool-timing-hook/state/ | head 看时间分布; 若大量文件 mtime 超过 1 h 仍在,说明回收未生效(可能 Pre/Post 均未运行)。

处置:按第 4 章「清空状态目录」(只删 [kdt]-*);然后确认挂载是否完整。

验证:再触发若干次调用后,目录内文件数回到小时量级;state/prune.log 会出现 [PRUNE][高危] 行(回收生效的证据)。

场景 4:埋点拖慢了工具调用

症状:感觉工具调用变慢。

诊断:单对 Hook 实测约 125 ms(含两次 python3 启动),与 p50 11 s 的步间隔相比可忽略。 若确实变慢,先按第 3 章速查表 ④ 看 dur_ms 是否异常——dur_ms 量的是工具自身,不含 Hook 开销。

处置:确认为 Hook 开销过大时,摘除 PostToolUse 一条(保留 Pre),或整体停用。

验证:停用后 hooks.json 挂载数为 0,日志停止增长。

9. 安全与权限

  • 不持有任何凭据,不联网,不连远端主机;
  • 日志内容不含工具参数(只记工具名、时长、会话 id、tool_use_id)——不会带出文件内容或命令正文;
  • 唯一的高危动作是回收过期文件,已限定前缀白名单 + 普通文件 + 不跟随符号链接,并写 prune.log 留痕;
  • 日志与状态文件的权限沿用本机默认(logs/ 现有文件为 664,属主 rain)。

10. 容量与性能

  • 单行日志约 200 B;按历史调用量级(约 300 次/天)估算,约 60 KB/天、20 MB/年,无需轮转;
  • state 目录稳态:一小时内调用量级(数十个空文件);
  • 写入为纯 append,无锁、无并发冲突(每行 < PIPE_BUF 一次写);
  • 已知瓶颈:无。

11. 附录

日志字段说明

字段 含义
ts 记录时刻(工具执行完成时刻,本地时间)
dur_ms 工具执行时长(毫秒);null 表示 Pre 未记录到开始时刻
tool Hook 侧工具名
llm_tool 请求体侧工具名(两者偶有差异,如请求体 SearchReplace → Hook 侧 Edit)
session / agent 会话与子智能体标识,用于按会话切分
id_src 配对键来源:tool_use_id(正常)/ fallback(事件缺唯一 id)
miss_pre 是否「只收到 Post」——恒为 true 即为挂载缺失的信号

相关文档

  • 变更档案:SOP.md
  • 时间侧全景分析:efficiency-sides-20261007/profile_time_sides.py
  • 系统侧(远端命令)口径:skill deployment-tool-engineering 的 remote_cmd_profile.py

修订记录

日期 变更
2026-10-07 首次建立

来源:/home/rain/Documents/services/token_proxy/tool-timing-20261007/RUNBOOK.md | 最后更新:2026-10-07 07:29(由 build/sync.py 同步,请勿在站点侧修改)