SOP:修 wrapup.py 的「时间墙」口径(turn_key 复用导致失真)¶
主题目录:
wrapup-turn-span-20261007/|变更日期:2026-10-07|实现体:cost_audit/wrapup.py
1. 背景¶
wrapup.py 是全局规则「收尾自查」指定的唯一取数入口(python3 cost_audit/wrapup.py),
其「时间墙」列取自 turn_key 的首步 → 末步跨度。
2026-10-07 一次实测报出 20656.9 s(5.7 h),与当轮实际执行(3.6 min)严重不符。核查发现根因:
turn_key并非「每轮唯一」,而是会被客户端大量复用。
实测(logs/usage.jsonl 全量 19,4xx 条):
| 指标 | 值 |
|---|---|
历史 turn_key 总数 |
1,205 |
| 其中被复用的 | 1,071(88.9%) |
| 最大复用样本 | 524ee3b9f3ce:807 步、跨度 21 天 |
| 该样本旧口径报出的时间墙 | 30,566.8 min(≈21 天) |
| 切分后 | 9.4 min |
即:88.9% 的 key 都可能把跨天/跨周的间隔算进「本轮时间墙」,此前报出的分钟级数字只是"恰好那一轮没有被复用"。
2. 目标¶
- 「时间墙」只计本轮真实活跃段:
turn_key聚合后,取最后一个连续段 (相邻请求间隔 >IDLE_SECONDS即断开),首步→末步; - 步数、合并、miss、guard 等其余「本轮」指标同源对齐——不把旧段的记录算进来;
- 输出格式逐字不变(规则里已固化列名与顺序),只改数值口径;
- 口径与既有实现一致:
efficiency-sides-20261007/profile_time_sides.py早就用 IDLE=900 s 切分,本次是把同一口径补进 wrapup。
3. 前置条件¶
- 可写
cost_audit/wrapup.py; python3可用;- 该脚本被全局规则硬引用,输出格式不得变(只改数值)。
4. 影响范围¶
| 项 | 说明 |
|---|---|
| 涉及对象 | 仅 cost_audit/wrapup.py(收尾自查取数脚本) |
| 是否停服 | 否;无窗口要求,下一次调用即生效 |
| 输出变化 | 「时间墙」与「本轮步数」在 turn_key 被复用时会显著变小且更真实;未复用时与旧值相同 |
| 不受影响 | 会话累计费用、四段占比、合并/未合并、miss 检测、guard 三数(除因 cur 收窄而同步更准外) |
5. 分步操作¶
步骤 1:备份(必做)¶
TS=$(date +%Y%m%d-%H%M%S)
cp -p ~/Documents/services/token_proxy/cost_audit/wrapup.py \
~/Documents/services/token_proxy/wrapup-turn-span-20261007/backup/home/rain/Documents/services/token_proxy/cost_audit/wrapup.py.bak."$TS"
步骤 2:加 active_tail() 并改 cur¶
四处改动,必须成套(缺任一处会出现"时间墙切了、步数没切"的错位):
- 新增常量
IDLE_SECONDS = 900与函数active_tail(rows):自尾部回溯, 遇到相邻间隔 >IDLE_SECONDS即停,返回最后一个活跃段(内部按ts重排,不依赖输入顺序); cur = active_tail([r for r in usage if r.get("turn_key") == turn])—— 步数、费用、合并、miss、guard 都由它派生,故切一处即可全部对齐;cur_shape按ts与cur对齐(req_shape与usage同源 ts),避免旧段的指纹污染本轮合并/miss 统计;- 文档头两处口径说明同步改写(写清"为什么切分"与实测证据)。
坑:active_tail 内部必须排序——usage 的读入顺序虽基本是时间序,但不能依赖;
且 len(rows) < 2 要原样返回,否则单步轮会被切成空段。
cd ~/Documents/services/token_proxy
python3 -m py_compile cost_audit/wrapup.py && echo "语法通过"
grep -n "IDLE_SECONDS\|active_tail" cost_audit/wrapup.py # 预期:常量 1 处 + 定义 1 处 + 调用 1 处
步骤 3:验证(构造用例 + 真实数据)¶
cd ~/Documents/services/token_proxy && python3 - <<'PY'
import importlib.util, json
from datetime import datetime, timedelta
from collections import Counter
spec = importlib.util.spec_from_file_location("w", "cost_audit/wrapup.py")
m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m)
base = datetime(2026, 10, 7, 8, 0, 0)
fmt = lambda d: d.strftime("%Y-%m-%d %H:%M:%S")
rs = lambda ts: [{"ts": t} for t in ts]
normal = [fmt(base + timedelta(seconds=s)) for s in (0, 10, 25, 40)]
print("① 正常轮段长 %d(期望 4)" % len(m.active_tail(rs(normal))))
seg = m.active_tail(rs([fmt(base - timedelta(days=14))] + normal))
print("② 复用轮段长 %d、首步 %s(期望 4 / 新段首)" % (len(seg), seg[0]["ts"]))
usage = [json.loads(l) for l in open("logs/usage.jsonl", encoding="utf-8") if l.strip()]
tk = usage[-1].get("turn_key")
allr = [r for r in usage if r.get("turn_key") == tk]
print("③ 真实本轮 %s:全量 %d 步 → 活跃段 %d 步" % (tk, len(allr), len(m.active_tail(allr))))
print(" 旧口径 %.1f min → 新口径 %.1f min" % (m.span_minutes(allr), m.span_minutes(m.active_tail(allr))))
c = Counter(r.get("turn_key") for r in usage if r.get("turn_key"))
print(" 复用面:%d key / 复用 %d(%.1f%%)" % (len(c), sum(1 for v in c.values() if v > 1), 100.0*sum(1 for v in c.values() if v > 1)/len(c)))
PY
预期:① 4 / ② 4 且首步为新段首 / ③ 新旧差一个数量级以上(复用严重时)。
失败判断:② 段长 >4 → active_tail 的断点判断失效(核对 gap > IDLE_SECONDS 的方向)。
步骤 4:实跑确认输出格式未变¶
cd ~/Documents/services/token_proxy && python3 cost_audit/wrapup.py | tail -6
预期:仍是两行表头 + 一行数据、六列顺序不变;「时间墙」为分钟级(不再出现 5 位数)。
6. 验证¶
| 层级 | 命令 | 预期 |
|---|---|---|
| 语法 | python3 -m py_compile cost_audit/wrapup.py |
无输出 |
| 构造用例 | 步骤 3 的 ①② | 4 / 4(且取新段) |
| 真实数据 | 步骤 3 的 ③ | 新旧口径差异显著 |
| 格式 | 步骤 4 | 六列不变、时间墙分钟级 |
7. 回滚¶
备份位置:wrapup-turn-span-20261007/backup/home/rain/Documents/services/token_proxy/cost_audit/wrapup.py.bak.20261007-080221,
台账见 backup/README.md。
cp -p ~/Documents/services/token_proxy/wrapup-turn-span-20261007/backup/home/rain/Documents/services/token_proxy/cost_audit/wrapup.py.bak.20261007-080221 \
~/Documents/services/token_proxy/cost_audit/wrapup.py
python3 -m py_compile ~/Documents/services/token_proxy/cost_audit/wrapup.py && echo "回滚后语法通过"
回滚后验证:active_tail 不再存在(grep -c active_tail 为 0),时间墙恢复为"首→末步"的旧口径。
8. 风险说明¶
| 风险 | 缓解 |
|---|---|
| 破坏被全局规则硬引用的输出格式 | 只改数值口径与 cur 的来源,未动任何 print 语句;步骤 4 逐列核对 |
IDLE_SECONDS=900 把真实的长步骤判为断开 |
900 s = 15 min,远大于任何单步耗时(p99 ≈ 361 s),不会误断 |
active_tail 排序依赖缺失 |
函数内显式 sorted(..., key=ts);len < 2 原样返回 |
| 步数因切分变小、被误读为"这几天变快了" | 切分后才才是本轮真实步数;旧值含旧段记录,本就偏高 |
| 只改时间墙、未改步数 | 本次直接改 cur(两者同源),不会出现半切分;步骤 3 的 ③ 同时核对两者 |
附录:交付物清单¶
| 文件 | 说明 |
|---|---|
cost_audit/wrapup.py(改) |
新增 IDLE_SECONDS + active_tail();cur / cur_shape 改口径;文档头同步 |
wrapup-turn-span-20261007/SOP.md |
本文档 |
wrapup-turn-span-20261007/backup/ |
脚本备份与台账 |
来源:
/home/rain/Documents/services/token_proxy/wrapup-turn-span-20261007/SOP.md| 最后更新:2026-10-07 08:03(由build/sync.py同步,请勿在站点侧修改)