跳转至

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. 目标

  1. 「时间墙」只计本轮真实活跃段:turn_key 聚合后,取最后一个连续段 (相邻请求间隔 > IDLE_SECONDS 即断开),首步→末步;
  2. 步数、合并、miss、guard 等其余「本轮」指标同源对齐——不把旧段的记录算进来;
  3. 输出格式逐字不变(规则里已固化列名与顺序),只改数值口径;
  4. 口径与既有实现一致: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

四处改动,必须成套(缺任一处会出现"时间墙切了、步数没切"的错位):

  1. 新增常量 IDLE_SECONDS = 900 与函数 active_tail(rows):自尾部回溯, 遇到相邻间隔 > IDLE_SECONDS 即停,返回最后一个活跃段(内部按 ts 重排,不依赖输入顺序);
  2. cur = active_tail([r for r in usage if r.get("turn_key") == turn]) —— 步数、费用、合并、miss、guard 都由它派生,故切一处即可全部对齐;
  3. cur_shape 按 ts 与 cur 对齐(req_shape 与 usage 同源 ts),避免旧段的指纹污染本轮合并/miss 统计;
  4. 文档头两处口径说明同步改写(写清"为什么切分"与实测证据)。

坑: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 同步,请勿在站点侧修改)