@dsh-external/dsh-usage-stats
DSH 用量与工具调用统计(token 分桶、费用估算、工具调用次数排行榜、按天趋势) 适合需要分析累计Token消耗、费用趋势及工具调用排行榜的用户。
Other repositories with this package name
Install
This plugin has no verified bundle, or compatibility checks failed. Read the repository notes first. Read the full README ↗
README
Read the full 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)。