在输入区实时查看首字延迟、生成速度与 Token 用量
30/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/24 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add hansomgar/dsh-recent-usage-metrics该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:仅索引
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
DeepSeek Harness 的极客模式请求指标:在输入框状态栏实时显示 TTFT、token 速度和 token 数,外加一个毛玻璃效果的历史抽屉,记录最近 10 次请求。
README
由 DeepSeek 最新模型翻译生成dsh-recent-usage-metrics
DSH 的极客级请求指标。它在 设置 → 通用 → 性能与用量 里加入第三档 极客,选中后,输入区状态行会常驻显示最新一次请求的实时读数,并可用一个磨砂玻璃抽屉查看最近 10 次请求的完整指标。
简体中文 · English
抽屉:最近请求指标(中文)
上图是抽屉展开的样子:顶部是正在生成的那一条(首字在增长,速度列显示 —),下面按新到旧列出最近 10 次请求,底部一行是它们的平均值。
最下方是状态栏里的气泡,与抽屉各列一一对齐。
界面为中英双语,跟随 DSH 的语言设置。英文界面下的同一抽屉:
抽屉:最近请求指标(英文)
你会得到什么
设置 → 通用 → 性能与用量 现在有三档:
| 档位 | 行为 |
| 简洁 | 原生行为,不变 |
| 详细 | 原生行为,不变 |
| 极客 | 本文下面描述的全部内容 |
简洁与详细仍写入原生的 ui-chat → performanceUsage 字段,因此行为与原来完全一致,也和原生行的数据保持同步。极客是一个独立的持久标记,因为原生 Chat 的 schema 只接受两个值。
展开完整文档(README 剩余 1 节)
dsh-recent-usage-metrics
DSH 的极客级请求指标。它在 设置 → 通用 → 性能与用量 里加入第三档 极客,选中后,输入区状态行会常驻显示最新一次请求的实时读数,并可用一个磨砂玻璃抽屉查看最近 10 次请求的完整指标。
简体中文 · English
抽屉:最近请求指标(中文)
上图是抽屉展开的样子:顶部是正在生成的那一条(首字在增长,速度列显示 —),下面按新到旧列出最近 10 次请求,底部一行是它们的平均值。
最下方是状态栏里的气泡,与抽屉各列一一对齐。
界面为中英双语,跟随 DSH 的语言设置。英文界面下的同一抽屉:
抽屉:最近请求指标(英文)
你会得到什么
设置 → 通用 → 性能与用量 现在有三档:
| 档位 | 行为 |
| 简洁 | 原生行为,不变 |
| 详细 | 原生行为,不变 |
| 极客 | 本文下面描述的全部内容 |
简洁与详细仍写入原生的 ui-chat → performanceUsage 字段,因此行为与原来完全一致,也和原生行的数据保持同步。极客是一个独立的持久标记,因为原生 Chat 的 schema 只接受两个值。
输入区状态行(位于原生 55 轮 2307 步 气泡左侧):
⚡ 2.3 s 445 tok/s 1024 tok 4.6 s 223 tok/s
依次为:首字时间 · 实时生成速度 · 输出 Token · 总用时 · 折算速度(输出 ÷ 总用时),各项之间只用空白分隔。该气泡复刻原生气泡:文字与图标同样使用 --dsw-alias-label-tertiary(原生气泡从不给图标上色),字号体系也相同。
高度由 padding + line-height 自然撑开,因此与原生的那一行齐平。
每个数字都以固定宽度写在数字元素自身上——绝不写在外层格子上,因为格子里还有单位,写窄了会把单位裁掉。估算标记是一个常驻占位的 1ch 元素,只切换可见性。
于是「3.1 长成 57.8」「速度从 328 掉到 9」「-- 占位」「~ 出现或消失」这些变化都让气泡宽度逐字节不变——居中的输入区永远不会位移。
这些宽度来自一份 433 条读数的真实会话日志的分布,每个格式化函数都写得不会超出自己的预算:
| 槽位 | 位数 | 为什么够 |
| 首字 / 总用时 | 4.5ch | 通常 57.8;超过 100 秒就打印整秒(126) |
| 速度 / 折算速度 | 4.5ch | 日志中 p50 85,最大 328 |
| 输出 Token | 5.5ch | p50 219;123456 折叠为 123k |
气泡与抽屉使用互不相交的类名(dsh-rum-part vs dsh-rum-col)。
曾因二者共用同一个单元格类名,抽屉的省略号规则把气泡的单位也裁掉了——这正是「数字后面跟着被截断的 s / tok/s」那次回归的成因。
抽屉的列宽是在运行时从气泡实测得到的,不是用 ch 推算的。ch 是字符 0 的推进宽度,而 tok/s 和细窄的 s 实际都比它窄,所以算术推算出来的列永远无法精确落在渲染出的格子下面。
打开抽屉时,插件读取每个 [data-metric] 格子的 getBoundingClientRect().width,按指标名各发布一个自定义属性(--dsh-rum-col-ttft、--dsh-rum-col-speed、 --dsh-rum-col-tokens、--dsh-rum-col-total、--dsh-rum-col-amortized),由行的 grid 消费。
气泡上挂一个 ResizeObserver,在字体缩放或指标变化后重新测量;测量落地前的那一帧由 ch 表达式兜底。
| 抽屉列 | 消费的变量 | 对齐到 |
| 首字 | --dsh-rum-col-ttft | 气泡的首字格 |
| 速度 | --dsh-rum-col-speed | 气泡的速度格 |
| 输出 Token | --dsh-rum-col-tokens | 气泡的 Token 格 |
| 总用时 | --dsh-rum-col-total | 气泡的总用时格 |
| 折算速度 | --dsh-rum-col-amortized | 气泡的折算速度格 |
还有两个细节才让边缘真正重合:
- 对齐内容边缘,不是容器边缘。 气泡的内容右内边距是 8px,而抽屉是自身 16px 加上一行的 6px。因此对齐两个容器会让数字明显内缩。两边的内边距都被实测(drawerContentInset / pillContentInset)并相减,抽屉里最后一个数字才与气泡里最后一个数字齐平。
- 富余宽度归到左侧。 抽屉保留 min-width 以免自己的表头被截断;每一行用 justify-content: end,把多余的空间推向左方,让最后一列保持贴右。
抽屉还会在下一个动画帧重新定位一次,因为首次定位可能跑在实测列宽生效之前。
点击气泡打开抽屉;再次点击、按 Esc、或点击外部即可关闭。没有悬停打开,也没有悬停提示。
抽屉——从状态行向上滑出,右边缘与该气泡对齐,position: fixed 挂在 document.body 上,因此不会被任何滚动容器裁剪。它的材质逐字复刻了原生统计弹窗: --dsw-specific-menu 底色(深色模式下是 #303136f0,即近乎不透明,正是原生数字背后的那层)、 --dsw-menu-backdrop-filter 的 40px/150% 模糊、--dsw-elevation-prominent 描边与阴影、 --dsw-radius-lg、以及 16px 内边距。它按最新在上的顺序列出最近十次请求,底部不加说明文字:
| # | 时间 | 首字 | 速度 | 输出 Token | 总用时 | 折算速度 |
| 1 | 18:42:07 | 2.3 s | 445 tok/s | 1024 tok | 4.6 s | 223 tok/s |
新请求从顶部滑入;被十行上限挤掉的那一行在同一帧内移除,而不是自己淡出。
离场动画会让第 11 行在整个动画期间参与布局,使抽屉先长高一行再缩回——每次插入都会看到一次高度跳动。打开抽屉时也只是建立基线,不播放动画,所以整个列表不会在打开的瞬间一起滑入。
某个步骤正在流式生成时,抽屉把它显示为历史上方的一条 live 行,状态读数也切换为 live 样式。
历史下方有一条分隔线,线下面是汇总行——对当前可见请求取平均值,复用同一套 grid,因此每个均值都落在它所汇总的那一列下面。完全没有样本的列(比如中断请求没有首字时间)打印破折号,而不是容易误解的 0。
数字是怎么算出来的
一切都在浏览器内、从 Conversation 层本就维护的 Chat 快照推导得出——插件没有新增任何 Host 服务、会话事件或投影。
- 首字时间——timing.firstTokenTime − timing.stepStartTime,即请求发出的时刻与第一个可见增量到达的时刻。
- 总用时——timing.completedTime − timing.stepStartTime。
- 实时速度——输出 Token ÷ 解码时长(completedTime − firstTokenTime)。
- 输出 Token——上游自己的 usage.outputTokens,这是权威值。它读自 assistant/message.data.usage(已用真实会话日志核对:262 个已结算步骤全部携带它)。
reasoningTokens 已经包含在这个计数里,绝不再叠加。对于原样转发自身 usage 的适配器,也接受 OpenAI 风格的 completionTokens 拼写。
- 折算速度——输出 Token ÷ 总用时。
字符估算是真正的兜底,不是常规路径:只有当某个步骤的上游完全没有返回 usage 时才会用到(中断或失败的请求),这样的行会被标记 ~。
它的系数是标定出来的,不是猜的。内容并不同质:正文与推理大约 4 个 ASCII 字符/token,而工具调用参数是 JSON——短键名、标点、带引号的值——分词效率明显更差,约 2.5。
以真实会话日志 729 个已结算步骤为样本,与上游自己的 outputTokens 对比,取中位绝对对数误差:
| 规则 | 中位误差 | 估计 ÷ 实际(p50) |
| 全部共用 /4 | 0.361 | 0.70 |
| 正文 /4、工具参数 /2.5(现行) | 0.128 | 1.00 |
把两者拆开才是消除偏差的关键。旧的共用除数会系统性低估约 30%,这正是生成过程中实时折算速度读数偏低的原因;细网格搜索给出的最优值是 0.125,现行规则与之相差三千分之一。CJK 保留惯例的 1 token/字符:搜索建议了更低的代价,但样本中只有 45% 的步骤含任何 CJK,该参数在这里辨识度很差,拟合出来是噪声。
工具参数在流式过程中
legacy.partial.blocks 在一个调用进行期间不携带参数文本。存储层的 tool-call-delta 分支是原样复制 argsRaw,所以它一直是空字符串,直到 block-end 一次性填入。因此只统计 blocks 会让实时读数在整个写入阶段冻结——而这一阶段占据了真实输出的绝大部分:在一份会话日志的 874 个已结算步骤里,工具参数占估算 token 的中位数 93%,且 98% 的步骤都带有它们。
分片在调用期间确实存在。它们累积在调用自身的 args.chunks 视图上,对于仍处于 preparing 阶段的调用,legacy.runningCalls 会把它暴露出来,因此实时计数从那里读取,并按工具参数系数加权。这就是为什么写文件期间数字会持续攀升,而不是等到结束时才跳一下。
流式行本质上是近似的——Chat 快照只把正在运行的步骤暴露为 legacy.partial(turn、step、blocks),没有每步的起始时间。有两点让这个近似保持诚实:
- 步骤级的起点。 结算后的数值是 firstToken − stepStart,所以实时值必须用同一个起点。
若改用轮次起点,就会把该轮中更早的一切都算进首字时间里:在一份真实会话日志中,从轮次开始到后序步骤开始的间隔中位数 174 秒,最大达到 2720 秒。
这就是为什么实时读数可能在推理时显示几十秒,而步骤一结算却只剩几秒。
- 用工具结果,而不是 completedTime。 timing.completedTime 取自 assistant/message 事件,落在该步工具执行之前。把它当下一步的起点,就会把整个工具执行时间算进去:实测该间隔中位数 243 毫秒,最大 120 秒——这正是「生成中显示 6 秒、结算后变成 2 秒」的成因。而一步的最后一个 tool/result 距它自己的 step/end 不超过 1 毫秒,距下一步 step/start 中位数只有 9 毫秒,是目前浏览器侧能拿到的最紧起点。
归属用 seq 区间而不是时间戳:续写的日志会重放步骤,时间戳会重复。
实测 1113 个步骤,起点误差从 p50 100 ms / p99 20.8 s 降到 p50 9 ms / p99 486 ms,超过一秒的步骤从 114 个降到 1 个。
该步没有调用工具时,退回它自己的 completedTime(此时它就是这一轮的最终回答,后面不再有工具);一轮的第一步退回轮次起点(实测晚 26 ms)。
- 首个增量,而不是首段可见文本。 结算后的 firstTokenTime 也把推理与工具调用增量计入,而 legacy.partial 只在步骤有了可见内容后才出现——所以新的 partial key 出现的瞬间 就是首字时刻。
- 从 step/start 就开始计时。 Chat 的 legacy.partial 要等第一个可见块,实测距步骤开始中位数 1.9 秒(p90 2.9 秒)——而这段时间正是首字的主体。
所以只看 Chat 的话,数字会「一出现就是终值」,看不到增长。
轨迹域在 step/start 那一刻就建立了该步的 assistant 记录,其 requests[] 里带 status: "running" 与精确的 startedAt(step/start 属于 location boundary,会话装配层会把发布优先级从定义声明的 none 提升为 immediate)。
插件据此判断「本步正在进行」并取起点,于是首字在生成中由 now − startedAt 持续增长,首个 token 到达后改用 firstTokenTime − startedAt 锁定——与结算值是同一个减法。
回放历史的首字时间
从已有会话给抽屉回填数据时,首字列曾经是空的,尽管轨迹面板对同样的步骤能显示出数值。
原因是数据流事实,而不是计算缺失:timing.firstTokenTime 是流式累积状态。
Chat 存储从实时 token 增量中累积它,而会话日志不存 live chunk——一份真实日志里有 787 个已结算消息,assistant/live-chunk 事件是零个。
因此从该日志回放的步骤没有这个字段。
但这个值仍可恢复。每条已结算消息都携带 data.stream,即完整的 chunk 时间线,每个分片都有自己的时间戳;轨迹域正是从这里重建该时刻(firstTokenTime ?? assistantStreamFirstTokenTime(event.data.stream)),对每个打包的 run 用 time0 + dt 做前缀和。这就是为什么它的面板能给 Chat 快照无法定时的历史标上时间。
插件从轨迹快照读取这些时间——槽位条目通过标准 useTrajectory hook 访问它——并据此给自己的行定时。有两点后果值得知道:
- 轨迹域是另一个插件的投影,所以它的时间可能在插件完成回填之后才到达。
那些已存为无时间的行会在此时就地修复(backfill),而不是整个会话都停在 --。
- 曾经流式实时记录下来的值绝不会被覆盖:修复只触碰首字时间仍然缺失的行。
如果某个组装里没有轨迹域,useTrajectory 就是缺席的,插件不建立索引,无时间的行继续显示 --——即之前的行为,且不报错。
安装
在 dsh GUI 的插件管理器中安装,或: plugin_manager install_bundle,target 指向本目录
该 bundle 把本目录链接进配置档,因此改完 client/client.js 刷新页面即可生效。
宿主侧代码永远不需要重启,因为它本身就存在。
目录结构
| 路径 | 作用 |
| client/client.js | 全部功能:设置行、状态读数、抽屉 |
| lib/index.js | 空的 Host 半——让包成为一个 Loader 条目,从而发布 ./client |
| cordis.patch.yml | 唯一插入的那一行 Loader 条目 |
| locale/en.json、locale/zh.json | 插件管理器里显示的标题与说明 |
| icon.svg | 插件图标 |
| test/lifecycle.cjs | 在 DOM/hook shim 上跑真实的客户端:指标、十行上限、插入顺序、点击开合、清理 |
| test/repro-coldstart.cjs | 回归:冷页面必须首屏中文,且双向可切换 |
| test/repro-hmr.cjs | 回归:重载不得与存活的字典占用冲突 |
node test/lifecycle.cjs
node test/repro-coldstart.cjs
node test/repro-hmr.cjs
重启之后的语言
locale 被声明为硬依赖(inject: ['slots', 'locale']),apply 读取 ctx.locale。
用 ctx.get('locale') 去探测是一个真实缺陷,症状相当令人困惑:locale 插件本身要等待 connection、settings、remotes 这几个服务,因此冷页面时本插件可能先执行 apply,读到 undefined,跳过字典注册,然后经由一个没有字典的 t 渲染——表现为英文或原始 key,而且即便把语言切回去也仍然是英文。
configForms 是可选的(某个配置档可能在不带设置域的情况下组装本 bundle),所以用 ctx.inject(['configForms'], …) 等待它,而不是探测一次。若只探测一次,一旦该域晚到,设置行就会整个会话被锁死在兜底偏好上。两种选择都遵循插件规范:必需的服务放进 inject,可选的放进 ctx.inject。
字典占用被挂在 globalThis 上,好让热重载能在注册前释放上一代的占用;否则第二次 register 同一命名空间会抛错,让界面失去字典——同样的可见症状,另一条路径。
备注
- 该行以 priority: -1 和原生的 order: 30 注册进原生的 performance-usage 单元格,因此它渲染在同一位置,原生单元格是被遮蔽而不是重复。
- 所有比一次渲染活得更久的东西都创建在 ctx.effect 里,因此禁用插件会恢复原生行,并移除每一个监听器与样式。
- 历史与极客标记存放在 localStorage(dsh.recentUsageMetrics.*),在禁用/启用之间仍然保留。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
hansomgar/dsh-recent-usage-metrics
在输入区实时查看首字延迟、生成速度与 Token 用量
相关插件 · UI / 主题
破甲增强版JeffKeeping/dsh-purge-plus
dsh-purge 的非官方分支fork:适配 DeepSeek Harness 0.2.1-rc.2、破甲内核…
★ 387已汉化DeepSeek 客户端
71.9
无代码业务系统平台nocobase/nocobase
NocoBase 是什么
★ 24,804已汉化DSH
71.8
1Panel 服务器面板1Panel-dev/1Panel
全球 250 万+ 自托管用户共同喜爱。
★ 37,362已汉化DSH
71.4
终端编码代理esengine/DeepSeek-Reasonix
· 指南 · ACP · 扩展开发 · 规格 · 官方网站 · Discord · QQ
★ 36,076已汉化DSH
71.2
工作状态行动态ccch1mneyyy/working-activity
作者:chimney@ccch1mneyyy。社区项目,非 DeepSeek 官方项目。
★ 664已汉化DSH
71.2
多模型配置管家farion1231/cc-switch
跨平台桌面一体化助手,支持 Claude Code、Codex、OpenCode、DeepSeek Harness 等工具,唯一官网 ccswitch.io
★ 151,814已汉化DSH
71.1
这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。