@dsh-external/dsh-usage-stats
DSH 用量与工具调用统计(token 分桶、费用估算、工具调用次数排行榜、按天趋势) 适合需要分析累计Token消耗、费用趋势及工具调用排行榜的用户。
同名パッケージの別リポジトリ
インストール
検証済み bundle がないか、互換性チェックに失敗しています。先にリポジトリの説明を読んでください。 README 全文を読む ↗
ドキュメント
README 全文を読む ↗@dsh-external/dsh-usage-stats
DeepSeek Harness 的用量与工具调用统计插件:在 Web 设置页新增「用量统计」分区,展示 累计 token 用量(四桶)、估算费用(¥)、会话 / 轮次 / 步数、按工具名的调用次数与耗时排行榜、 按模型分布、以及近 14 天的按天趋势。
首次启用时回填全部历史会话,之后实时增量累计,跨重启不丢。
安装
git clone https://github.com/XIA-2005/dsh-usage-stats.git
cd dsh-usage-stats
export DSH_CHECKOUT=/path/to/deepseek-harness # 需要含 packages/ 与 node_modules/.bin/tsc 的源码检出
bash scripts/build.sh # host:link 依赖 + tsc → lib/
npm run build:client # client:tsdown → lib/client.js
构建完成后,在 DSH 注入器环境里 dev_build_plugin → dev_inject_plugin
即可(运行时注入,免重启,卸载即净)。
功能
| 区块 | 内容 |
|---|---|
| 总览卡片 | 总 tokens、估算费用 ¥、会话 / 轮次(另附步数)、工具调用总次数(另附工具种类数) |
| token 构成 | 未缓存输入 / 缓存读取 / 缓存写入 / 输出 四桶的绝对值、占比与条形图 |
| 工具调用排行 | Top N(默认 12):调用次数、占比条、按 tool/call → tool/result 配对的累计耗时 |
| 按模型 | 每个模型的调用次数、缓存读、输出、费用 —— 也是费用口径的自证(Pro 档单价为基价 3 倍) |
| 最贵的对话 | 按估算费用降序的会话排行(标题 / 创建日 / tokens / 费用),直接回答「钱花在哪个对话上」 |
| 用量趋势(可交互) | 7 / 14 / 30 / 90 天 / 全部 窗口切换;柱子 hover 出浮层,点击选中某天(再点取消) |
| 构成分析(交互饼图) | 环形图 4 个维度:token 构成 / 按模型 / 按工具 / 按对话;hover 扇区外移高亮 + 环心显示占比,点图例可隐藏某项 |
| 操作 | 刷新、增量扫描、重建统计(清空账本从头重放) |
点柱子选中某天 → 全板块联动:总览卡片、token 构成、三张表、饼图全部切到那一天,顶部出现「已选 2026-08-15 ✕」可一键取消;柱状图该柱高亮。选中态、窗口、饼图维度、图例开关都会在 3 秒轮询刷新后保持。
交互式图表
- 窗口:7 / 14 / 30 / 90 天 / 全部(
全部= 账本里所有有数据的日期,卡片与表格也随之切换口径)。 - 环形图:hover 时被指扇区沿角平分线外移、其余降到 35% 透明度,环心显示「名称 / 占比 · 数值」; 点击图例项隐藏/显示该扇区(隐藏状态按维度分别记忆)。
- 四个维度的取值口径:token 构成 = 四桶互斥计数;按模型 = 该视角下各模型的估算费用; 按工具 = 调用次数(取前 8,其余并入「其他」);按对话 = 费用(取前 6,其余并入「其他」)。
- 实现:纯 DOM + 手写 SVG(
src/client/chart.ts),不引入任何图表库 —— client bundle 由 tsdown 打进lib/client.js,第三方图表库会让体积成倍增长,而这里只需要一根柱子和一段圆弧。
数据来源与统计口径
- 实时:监听
session/event,折叠以下事件tool/call→ 按name计次;callId记入挂起表tool/result→ 与挂起配对,耗时累加到该工具名下assistant/message→usage四桶累加 + 按message.source.model计价step/end→ 步数;turn变化计一轮
- 历史:经官方
ctx.sessionPersistence服务(list→open(id,'read')→ 分页read)逐会话续读。 会话日志是session.jsonl.zstd,物理上为多个 zstd frame 串联(header frame + 每批事件 frame), Node 的createZstdDecompress解到第二帧即报ZSTD_error_prefix_unknown—— 因此必须走官方解码器, 插件不做任何自解析。 - token 四桶互斥:
inputTokens(未缓存输入)、cacheReadTokens、cacheWriteTokens三者相加才是 计费输入;reasoningTokens已包含在outputTokens内,不重复计入。 - 费用为估算:
cacheRead × 命中价 + (未缓存输入 + 缓存写入) × 未命中价 + 输出 × 输出价, 峰谷按事件自身时间戳判定(工作日北京时间 9–12 点、14–18 点为峰时;2026-08-23 起周末全天谷价), 因此历史回填也能还原当时的时段价。单价表集中在src/pricing.ts,调价只改该文件,然后点「重建统计」。 - 按天明细(天 × 模型 / 天 × 工具 / 天 × 会话)在折叠时一并写入,是交互式饼图与「选中某天」的数据源;
只保留最近 120 天(
DETAIL_KEEP_DAYS),更早的只留天汇总days。 - 子代理会话计入总量(它们真实消耗 token)。
- 去重:每个会话维护
consumedSeq游标,实时链路与回填路径在折叠前统一做seq /storages/session_projcache/sessions/.json→record.rows.title.val,是只读、防御性 依赖:读不到就退回显示 cwd + 短 id,不影响统计本身。 - 读取失败的会话会被跳过并在面板提示(本地历史遗留的旧命名格式目录实测有 1 个)。
- 工具耗时依赖
tool/call与tool/result在同一进程生命周期内配对;进程重启会丢失未配对的挂起项 (仅影响极少数跨重启的长任务耗时,次数计数不受影响)。 - 账本损坏时自动备份为
.dsh-usage-stats.corrupt-.json并从零重建。 - 单价为公开定价的估算值,以官方账单为准。
- 按天明细不追溯:
dayModels/dayTools/daySessions是 v0.1.0 新增维度,历史不会自动补算 —— 面板会提示「按天明细缺失 N 天」,点一次「重建统计」即可补齐(重放全部会话,约 30 秒)。
账本
%DSH_HOME%/.dsh-usage-stats.json(默认 ~/.dsh/.dsh-usage-stats.json),内存为权威、变更后防抖 2 秒
原子落盘(临时文件 + rename);卸载与重载前强制 flush。
{
"version": 1,
"totals": { "uncachedInputTokens": 0, "cacheReadTokens": 0, "cacheWriteTokens": 0,
"outputTokens": 0, "costCny": 0, "sessions": 0, "turns": 0, "steps": 0, "toolCalls": 0 },
"tools": { "": { "calls": 0, "ms": 0 } },
"days": { "YYYY-MM-DD": { /* 四桶 + costCny + toolCalls + modelCalls */ } },
"dayModels": { "YYYY-MM-DD": { "": { /* 四桶 + costCny + calls */ } } },
"dayTools": { "YYYY-MM-DD": { "": { "calls": 0, "ms": 0 } } },
"daySessions": { "YYYY-MM-DD": { "": { /* 四桶 + costCny */ } } },
"models": { "": { /* 四桶 + costCny + calls */ } },
"sessions": { "": { "consumedSeq": 0, "cwd": "...", "lastTurn": 0, "origin": "root",
"usage": { /* 四桶 + costCny */ }, "modelCalls": 0, "toolCalls": 0 } },
"backfill": { "done": true, "scanned": 0, "total": 0, "errors": 0, "running": false, "failedSessions": [] }
}
HTTP API
前缀 /dsh-usage-stats/api(由插件在 host 侧注册):
GET /summary?days=14&top=12→{ ok, generatedAt, totals, tools[], models[], sessions[], toolKinds, days[], backfill, meta }days上限 400;range=all返回账本里所有有数据的日期(不补零)meta.detailMissing/meta.detailMissingDays:有当日汇总却缺明细的天数(升级后提示重建用)
GET /days?from=YYYY-MM-DD&to=YYYY-MM-DD→ 按天明细{ ok, from, to, days[{ date, models[], tools[], sessions[] }] }(工具/会话各取 Top 20)。 面板只在窗口或选中日期变化时按需拉取(60 秒节流),不跟 3 秒主轮询。POST /rescan→ 增量扫描(补齐账本中尚无记录的会话)POST /rescan?rebuild=1→ 清空账本后从头重放(改定价 / 新增统计维度后使用)
构建与注入
export DSH_CHECKOUT=D:/deepseek-harness # 需含 packages/ 与 node_modules/.bin/tsc 的源码检出
bash scripts/build.sh # host:link 依赖 + tsc → lib/
npm run build:client # client:tsdown → lib/client.js
注入器环境下:dev_build_plugin → dev_inject_plugin (运行时注入,免重启,卸载即净)。
注:
npm run build:client需要 tsdown 可解析;若插件目录未装,可直接调用检出里的/node_modules/.bin/tsdown(在插件目录内执行)。lib/client/index.js是 tsc 的中间产物(类型声明用),真正的 client 入口是lib/client.js。
开发要点(踩过的坑)
- client 侧
ctx.slots.register(options, component)的 component 是第二个参数,且要包在ctx.slots.inject('', …)里、options.name必须是已知 slot 名(这里用settings.section)。 - 注入器的预检用字面形态
register({校验 slot 名,写成register(\n {会被误判为「缺合法 name」而阻断注入。 - host 侧不 import 任何
@deepseek-ai/*值(只用ctx取服务),因此不依赖 profile 里的包解析, 任何 profile 下都不会因缺依赖而挂起;sessionPersistence缺失时自动降级为「仅统计本次启动后的数据」。 - SVG 扇区做 hover 放大要用
translate(沿角平分线外移),不要用scale—— SVG 元素默认以用户 坐标系原点为变换基准,直接缩放会让扇区飞出可视区;环心文本用绝对定位 HTML 覆盖层,比 `` 好排版。 - 单个扇区占 100% 时不能用单段
A弧(起终点重合会不渲染),要走两段半弧。 - 交互状态(窗口 / 选中日期 / 饼图维度 / 图例开关)必须存在组件闭包里:3 秒轮询会重绘 DOM, 存在 DOM 或每次重建都会丢。
- 「明细是否缺失」不能用「明细表是否为空」判断:升级后当天就会产生新明细,历史缺失会被掩盖 ——
要逐日比对「有汇总但无任何明细」的天数(
countDaysMissingDetail)。