@kitsunemori96/open-session-metrics 0.0.0-stage → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +446 -2
- package/bin/osm.js +491 -0
- package/package.json +57 -4
- package/server/dist/index.js +3894 -0
- package/web/dist/assets/index-CXuzIF_g.css +1 -0
- package/web/dist/assets/index-Dr7sP5of.js +228 -0
- package/web/dist/assets/index-Dr7sP5of.js.map +1 -0
- package/web/dist/fonts/LICENSE-symbols.txt +76 -0
- package/web/dist/fonts/instrument-symbols.woff2 +0 -0
- package/web/dist/fonts/inter-latin.woff2 +0 -0
- package/web/dist/fonts/jetbrains-mono.woff2 +0 -0
- package/web/dist/index.html +18 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kitsuneMori96
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,447 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Open Session Metrics
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
把本机 CLI 的编码会话物化成指标库,再用一个 Web 面板回看:跑了哪些会话、花了多少
|
|
4
|
+
Token / 积分、调用了哪些工具、每个工具失败过几次、成本落在哪个项目上。
|
|
5
|
+
|
|
6
|
+
支持两种数据源,两库并存、页眉一处切换:
|
|
7
|
+
|
|
8
|
+
- **OpenCode**:`~/.local/share/opencode/opencode.db`(SQLite,只读),成本单位美元;
|
|
9
|
+
- **Qoder CLI**:`~/.qoder/projects/**/*.jsonl`(逐行转录),成本单位积分。
|
|
10
|
+
|
|
11
|
+
两种来源归一成同一套会话 / 轮次 / 工具记录,聚合、切片、图表与正文面板完全共用。
|
|
12
|
+
|
|
13
|
+
功能范围对齐 [`opencode-top`](https://github.com/kitsuneMori96/open-session-metrics/blob/master/docs/opencode-top-parity.md)(Sessions / Tools / Overview
|
|
14
|
+
三屏加正文面板),呈现方式按浏览器重排:**一个页面、一条全局切片、图表之间互相下钻**。
|
|
15
|
+
窗口、过滤器、对比对象、散点坐标轴都写在 URL 里,链接可以直接发给别人或存进书签。
|
|
16
|
+
|
|
17
|
+
数据只在本机流转,API 默认绑定 `127.0.0.1`。
|
|
18
|
+
|
|
19
|
+
## 安装
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm i -g @kitsunemori96/open-session-metrics
|
|
23
|
+
osm # 起服务并打开浏览器
|
|
24
|
+
osm status # 谁在跑、数据在哪
|
|
25
|
+
osm logs # 跟踪服务日志
|
|
26
|
+
osm stop # 停掉后台服务
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
> 0.3.0 起项目从 `opencode-session-metrics` 改名为 **Open Session Metrics**(包名
|
|
30
|
+
> `@kitsunemori96/open-session-metrics`,CLI 仍是 `osm`)。旧包会标记 deprecated;
|
|
31
|
+
> 已全局安装旧包的用户重装一次即可。旧的数据目录
|
|
32
|
+
> `~/.local/share/opencode-session-metrics` 会被继续沿用,不迁移、不丢历史。
|
|
33
|
+
|
|
34
|
+
要求 **Node 24+**(使用内置 `node:sqlite`,无原生依赖),且本机至少有一种数据源:
|
|
35
|
+
`~/.local/share/opencode/opencode.db`(OpenCode)或 `~/.qoder/projects`(Qoder CLI)。
|
|
36
|
+
默认源自动探测,也可以 `OSM_SOURCE=qoder osm` 指定,或启动后在页眉切换
|
|
37
|
+
(见[数据源](#数据源))。全局安装把指标库写在
|
|
38
|
+
`~/.local/share/open-session-metrics`(`OSM_DATA_DIR` 或 `osm --data` 可改),
|
|
39
|
+
升级包不会动到它。`osm run` 前台运行,`--port` / `--host` 改绑定。
|
|
40
|
+
|
|
41
|
+
从源码运行见[快速开始](#快速开始)。
|
|
42
|
+
|
|
43
|
+
## 界面
|
|
44
|
+
|
|
45
|
+
下面的截图取自本机的真实数据(OpenCode 源),不是合成图;截图为了看清面板用的是 30 天切片(默认窗口是 24 小时)。
|
|
46
|
+
Qoder 源读同一套面板:成本列由 `$` 换成 `cr`、`$/Mtok` 换成 `cr/Mctx`,没有 token class 的库
|
|
47
|
+
堆叠维度默认按模型(Token 语义见[指标定义](#指标定义))。
|
|
48
|
+
|
|
49
|
+
### Overview — 总量与形状
|
|
50
|
+
|
|
51
|
+
KPI 行(各带与上一等长窗口的对比)、按模型堆叠的 Token 时间序列、7×24 活跃热力图、
|
|
52
|
+
按小时的轮次分布、会话散点、项目 / 模型 / 工具 / agent 排行、效率面板、最重会话。
|
|
53
|
+
|
|
54
|
+

|
|
55
|
+
|
|
56
|
+
### Sessions — 全部会话一张表
|
|
57
|
+
|
|
58
|
+
可排序、可展开(根会话下挂子代理会话);过滤、排序、翻页都由 SQL 完成,
|
|
59
|
+
一次取 500 个根会话(连它们的子树),「load more」接着下一段。点一行进详情。
|
|
60
|
+
|
|
61
|
+

|
|
62
|
+
|
|
63
|
+
### Session 详情 — 时间线、工具、正文
|
|
64
|
+
|
|
65
|
+
逐轮 Token、上下文填充、输出 Token 三条曲线与正文面板共享一个游标;框选时间线只留选中的轮次。
|
|
66
|
+
|
|
67
|
+

|
|
68
|
+
|
|
69
|
+
### Tools — 工具取证
|
|
70
|
+
|
|
71
|
+
左侧工具表,右侧选中工具的调用时间桶、延迟直方图、agent / 项目分布、错误文案聚类与原文。
|
|
72
|
+
|
|
73
|
+

|
|
74
|
+
|
|
75
|
+
### Compare — 两个会话并排
|
|
76
|
+
|
|
77
|
+
逐项差值表、按轮次序号对齐的输出曲线、工具构成对比。
|
|
78
|
+
|
|
79
|
+

|
|
80
|
+
|
|
81
|
+
## 功能
|
|
82
|
+
|
|
83
|
+
### 全局切片(SliceBar)
|
|
84
|
+
|
|
85
|
+
顶端一条:`24h / 7d / 30d / 90d / 1y / all` 六个预设(默认 `24h`),加
|
|
86
|
+
`project / model / agent` 三个维度过滤。切片是**符号化**的:选预设时 URL 里只记
|
|
87
|
+
`range=7d`(永远是相对当前时间),只有手动框选图表才会写入绝对的 `from` / `to`。
|
|
88
|
+
切片的每一次变化都直接改 URL,所有面板(KPI、图、表、正文)一起重算。
|
|
89
|
+
|
|
90
|
+
时间轴的单位默认自动:三日以内按小时、半年以内按天、再长按周,
|
|
91
|
+
列数超过 200 就继续往粗里退,实际用的单位写在图的右上角(`gran=5m` 这类显式指定
|
|
92
|
+
仍然生效,但同样受这个上限约束——十年历史不会因为一句 `gran=5m` 就吐出几十万列)。
|
|
93
|
+
|
|
94
|
+
### 屏与路由
|
|
95
|
+
|
|
96
|
+
四个导航屏(`1`–`4`)加两个详情路由,共用同一套切片、表头与键盘。
|
|
97
|
+
|
|
98
|
+
- **Overview**(`/overview`):KPI(Token / 成本 / 轮次 / 工具 / 错误 / 会话 / 输出 /
|
|
99
|
+
增删行,各带与上一等长窗口的对比;没有更早的窗口时对比显示破折号而不是 +100%)、
|
|
100
|
+
按模型堆叠的 Token 时间序列(可框选重新定窗口)、
|
|
101
|
+
7×24 活跃热力图、24 小时分布、会话散点(x/y 可选 token / cost / turns / duration /
|
|
102
|
+
output rate / context / errors / updated,对数轴可切)、项目 / 模型 / 工具 / agent 排行、
|
|
103
|
+
效率面板(缓存命中、生成速度、轮次耗时 p50/p90/p99、上下文填充、未完成数)、
|
|
104
|
+
最重会话列表(hover 时在散点里高亮同一个点;散点与列表都是最重的 200 个会话样本,
|
|
105
|
+
其余在 Sessions 里翻页看)。
|
|
106
|
+
- 图表手势:**在时间序列上拖拽 = 重新定义窗口**;**点任意排行条 = 按该维度过滤**;
|
|
107
|
+
点散点 = 进该会话详情。
|
|
108
|
+
- **Sessions**(`/sessions`):全部会话一张可排序、可展开的表(根会话下挂子代理会话,
|
|
109
|
+
缩进显示层级)。列:标题 / 项目 / agent / 模型 / Token / 成本 / 轮次 / 工具调用 /
|
|
110
|
+
错误 / 时长 / 上下文占用 / 更新时间。点一行进详情。过滤、排序与分页是服务端的:
|
|
111
|
+
一次最多取 500 个根会话(连同各自的子树)。普通库一个请求就给全,所以看不到任何
|
|
112
|
+
翻页控件;只有十年这种量级才会分页,滚到表尾自动续下一页,底部只写「已显示 X / 共 Y」。
|
|
113
|
+
过滤命中子会话时会保留它的祖先,状态栏的合计始终描述整个命中集合,而不是已加载的部分。
|
|
114
|
+
- **Session 详情**(`/sessions/:id`):Token 明细、形状(轮次、工具调用、墙钟、生成速度、
|
|
115
|
+
上下文窗口与峰值)、工具用量、agent 链(父子/兄弟,可跳转)、逐轮 Token 时间线
|
|
116
|
+
(框选 = 只留这几轮,点一轮 = 聚焦该轮)、正文面板(见下)、每轮花费。
|
|
117
|
+
- **Tools**(`/tools`、`/tools/:name`):左侧工具表(调用数、失败数、p50 / p95 / max /
|
|
118
|
+
均值耗时、涉及会话与 agent 数,列可排序),右侧选中工具的调用时间桶、延迟直方图、
|
|
119
|
+
按 agent / 项目的分布、错误文案聚类(URL / 路径 / 数字归一后归并)、最近错误原文、写回的行数。
|
|
120
|
+
- **Compare**(`/compare?a=&b=`):任选两个会话并排:逐项差值表(Token / 成本 /
|
|
121
|
+
$ per Mtok / 轮次 / 工具 / 失败 / 墙钟 / 生成速度 / 峰值上下文 / 增删行,
|
|
122
|
+
右侧为相对左侧的百分比变化)、按轮次序号对齐的输出 Token 曲线、工具构成对比。
|
|
123
|
+
|
|
124
|
+
### 正文面板(Session 详情)
|
|
125
|
+
|
|
126
|
+
用户 prompt(`>` 起头)与助手轮次交替展开:轮次头(模型、agent、输入/输出、耗时、
|
|
127
|
+
时刻、ctx 占用)、助手正文、推理块(折叠成一行预览)、每次工具调用一行
|
|
128
|
+
(`✓ / × / ◌` + 名称 + 标题 + 耗时),展开后看入参与回显(回显最多 40 行)。
|
|
129
|
+
|
|
130
|
+
默认过滤是 `text`——只留两侧的正文,不含思考与工具;连续几轮只有工具和思考、
|
|
131
|
+
没有正文时,折成一行 `▸ 14 turns · 20 tools · 15:14:54–15:16:21 · +336.2K tok`
|
|
132
|
+
(点一下或 `↵` 展开轮次头)。`tools` / `errors` 只留工具行,没有内容的轮次头
|
|
133
|
+
直接不显示。`all` 才是全部。工具栏给出 `rows 起–止/总`、过滤循环与命中数。
|
|
134
|
+
|
|
135
|
+
### 键盘
|
|
136
|
+
|
|
137
|
+
| 键 | 作用 |
|
|
138
|
+
| --- | --- |
|
|
139
|
+
| `1` `2` `3` `4` | Overview / Sessions / Tools / Compare |
|
|
140
|
+
| `/` 或 `⌘K` | 命令面板:跳转、切窗口、套用过滤、检索全库正文并跳会话 |
|
|
141
|
+
| `r` | 触发一次增量物化 |
|
|
142
|
+
| `esc` | 关闭面板 / 复位焦点 |
|
|
143
|
+
| `j` `k` `g` `G` | 正文面板里移动游标(虚拟滚动,只渲染可见行) |
|
|
144
|
+
| `n` `N` | 跳到下一个 / 上一个命中 |
|
|
145
|
+
| `f` | 过滤循环 `text → all → errors → tools`(默认 `text`:prompt + 正文) |
|
|
146
|
+
| `Enter` | 展开 / 折叠当前行的工具调用、推理块或折起的轮次 |
|
|
147
|
+
| `e` | 只看游标所在的那一轮(正文面板内的轮次过滤) |
|
|
148
|
+
|
|
149
|
+
底部状态栏给出当前屏可用的动作。
|
|
150
|
+
|
|
151
|
+
### 动效
|
|
152
|
+
|
|
153
|
+
一套语言,取自数字滚轮那条曲线:一条 `cubic-bezier(0.4,0,0.2,1)`、四档时长
|
|
154
|
+
(90 / 120 / 170 / 260ms)、六个动词——Roll(数字)、Press(指针态只变色)、
|
|
155
|
+
Ink(提示卡与面板落位)、Turn(字形转向)、Pulse(只给同步与首屏骨架)、
|
|
156
|
+
Wipe(图表按内容指纹重画)。
|
|
157
|
+
|
|
158
|
+
不弹、不放大、不滑整页;首屏、轮询、滚动追加都不播动画;框选、提示卡跟随、
|
|
159
|
+
坐标轴刻度这几处逐帧跟手的地方一律不做缓动。`prefers-reduced-motion` 下全部降到 1ms。
|
|
160
|
+
规则与实现见 [docs/motion.md](https://github.com/kitsuneMori96/open-session-metrics/blob/master/docs/motion.md),`pnpm -F @osm/web test` 会跑一遍静态审计。
|
|
161
|
+
|
|
162
|
+
## 工作原理
|
|
163
|
+
|
|
164
|
+
两种源各有一个适配器,产出同一套归一化记录;适配器之上(聚合、rollup、正文、API)
|
|
165
|
+
不知道会话来自哪个 CLI:
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
OpenCode opencode.db (只读) ──▸ SourceReader ──┐
|
|
169
|
+
├──▸ SrcSession / SrcTurn / SrcTool
|
|
170
|
+
Qoder CLI ~/.qoder/projects/*.jsonl ─▸ QoderSource ─┘ (归一化记录)
|
|
171
|
+
│ 物化
|
|
172
|
+
┌───────────────┴───────────────┐
|
|
173
|
+
metrics.db(美元) metrics-qoder.db(积分)
|
|
174
|
+
└───────────────┬───────────────┘
|
|
175
|
+
│ node:sqlite
|
|
176
|
+
Express API :8787
|
|
177
|
+
│
|
|
178
|
+
Web 面板 (Vite/React)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
每个指标库里有 `turns · tools · parts · events · sessions · flow · heat · models`,
|
|
182
|
+
以及按天滚存的派生表(见[数据表](#数据表))。指标不从源库实时计算:图表要的每个数字
|
|
183
|
+
(时间桶、模型堆叠、热力图、百分位、会话排行)都在服务端用 SQL 聚合好,
|
|
184
|
+
一次请求返回当前切片的完整快照,浏览器只负责画。物化由源库水位驱动,只有源库出现
|
|
185
|
+
新数据时才重建,且只重算受影响的会话:OpenCode 的水位是 `MAX(消息时间, 会话更新时间)`,
|
|
186
|
+
Qoder 是会话文件的 mtime——不维护偏移文件,避免与 CLI 自己的写入竞争;子代理转录
|
|
187
|
+
随父会话一起重建。OpenCode 兼容源库 v1 与 v2 两代 schema。
|
|
188
|
+
|
|
189
|
+
查询不直接扫 `turns`。宽窗口(按天及更粗的分桶)读的是按天滚存的四张派生表:
|
|
190
|
+
(会话 × 天 × 项目/模型/agent)的合计、按小时的轮次、按天的工具调用与延迟直方图。
|
|
191
|
+
十年历史因此是几千行而不是几十万行;窗口两端被切开的半天仍从 `turns` 现算,
|
|
192
|
+
两半相加即精确。滚存不属于 `SCHEMA`,随物化在同一事务里更新,旧库在打开时按
|
|
193
|
+
`rollVersion` 就地补建——不需要重新读源库。百分位是唯一仍按轮扫描的部分:
|
|
194
|
+
细直方图的桶数在十年尺度上等同于轮数,预先聚合并不省任何东西。
|
|
195
|
+
|
|
196
|
+
`opencode-top` 每次刷新都重新解析源库的 JSON 正文;本项目的约定相反,正文在物化时就落进
|
|
197
|
+
`parts` 表,接口只读指标库。代价是库变大(正文占 108MB 里的绝大部分),换来的是
|
|
198
|
+
查询不碰源库、面板可以在会话之间随便点。
|
|
199
|
+
|
|
200
|
+
正文落库时有长度上限(text / reasoning 2000 字符、工具入参 1500、工具回显 3000),
|
|
201
|
+
超出部分面板本来就画不出来(回显最多展示 40 行)。
|
|
202
|
+
|
|
203
|
+
## 数据源
|
|
204
|
+
|
|
205
|
+
面板可以读两种来源,两库并存、随时切换:
|
|
206
|
+
|
|
207
|
+
| 来源 | 位置 | 成本单位 | 物化库 |
|
|
208
|
+
| --- | --- | --- | --- |
|
|
209
|
+
| OpenCode | `~/.local/share/opencode/opencode.db`(SQLite,只读) | 美元 | `metrics.db` |
|
|
210
|
+
| Qoder CLI | `~/.qoder/projects/<encoded-cwd>/<sessionId>.jsonl`(JSONL) | 积分 | `metrics-qoder.db` |
|
|
211
|
+
|
|
212
|
+
数据源是**作用域**,不是筛选器:它是面板最高一层决定(高于路由、窗口、facet),在页眉右端
|
|
213
|
+
一次性选定并记住,不写进 URL。两库各存一份切片,切回来还是原来的窗口与筛选。
|
|
214
|
+
完整设计(线框、像素账、API 契约、验收门)见
|
|
215
|
+
[docs/source-scope.md](https://github.com/kitsuneMori96/open-session-metrics/blob/master/docs/source-scope.md)。
|
|
216
|
+
|
|
217
|
+
启动时的默认源:`OSM_SOURCE=opencode\|qoder` → 显式给了 `OSM_SOURCE_DB`(按 OpenCode 处理)→
|
|
218
|
+
存在 `opencode.db` → 存在 `~/.qoder/projects`。启动只物化默认源;另一个库在你第一次切过去时
|
|
219
|
+
才建(Qoder 约 4 秒),之后由 2 分钟一轮的增量同步跟着走。
|
|
220
|
+
|
|
221
|
+
Qoder 没有会话数据库,转录是逐行 JSONL。两条格式陷阱决定了解析方式:一次模型响应会被
|
|
222
|
+
拆成 2–4 个 `assistant` 事件(只有最后一条带 `usage`,必须按 `message.id` 合并),
|
|
223
|
+
子代理的转录写在 `<sessionId>/subagents/` 下(`sessionId` 仍是父会话,必须并入父会话而不是
|
|
224
|
+
另起一个会话)。token 字段恒为 0,上下文占用由 `context_usage_ratio` 乘该模型的分母还原
|
|
225
|
+
(`runtime-config` 钉过窗口就取钉的值,否则取模型目录的 `max_input_tokens`——
|
|
226
|
+
Qwen 180K、DeepSeek 1M,不是选择器显示的 200K);积分按整数微积分
|
|
227
|
+
(`credits_micro`,×1e6)入库,`cost` 列存同一数值供现有聚合使用。
|
|
228
|
+
完整规则与验收门见 [docs/qoder-source.md](https://github.com/kitsuneMori96/open-session-metrics/blob/master/docs/qoder-source.md)。
|
|
229
|
+
|
|
230
|
+
成本单位由 `meta.unit` 声明、`/api/sources` 与 `/api/status` 透出,面板在渲染任何数字之前
|
|
231
|
+
按当前库的单位把 `$` 换成 `cr`,不需要手工切换。
|
|
232
|
+
|
|
233
|
+
## 快速开始
|
|
234
|
+
|
|
235
|
+
从源码运行:要求 Node 24+、pnpm,且本机已有 `opencode.db` 或 `~/.qoder/projects`。
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
pnpm install
|
|
239
|
+
pnpm serve # 构建 web+server,重启在 http://127.0.0.1:8787,就绪后才返回
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
`pnpm serve` 是推荐的启动方式,它把「构建」和「重启」绑成一件不可分的事:
|
|
243
|
+
|
|
244
|
+
1. `pnpm build`(web → server)。构建失败就到此为止,旧进程继续服务,不会留下半个产物。
|
|
245
|
+
2. 停掉占用端口的那个进程——**包括没有 pid 文件、手工 `node server/dist/index.js` 起的**。
|
|
246
|
+
3. detached 重启,等到 `/api/status` 真正应答才返回,并打印 pid、数据目录与 URL。
|
|
247
|
+
|
|
248
|
+
这一步之所以必要:前端产物与服务端产物分别构建,而 `express.static` 会把未知的 `/api/*`
|
|
249
|
+
回落到 `index.html`。只换前端不换后端时,新 bundle 请求 `/api/sources` 收到 200 HTML,
|
|
250
|
+
解析失败后静默降级——页眉的数据源控件会退化成纯文本。`pnpm serve` 从根上消掉这种错配。
|
|
251
|
+
|
|
252
|
+
首次启动若无指标库会自动物化(OpenCode 全量几分钟,Qoder 约 4 秒);之后按水位增量更新。
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
pnpm serve -- --no-open # 不打开浏览器
|
|
256
|
+
pnpm serve -- --port 8899
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
进程管理交给同一个脚本:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
node bin/osm.js status # 端口、pid、数据目录、bundle 构建时间;进程早于产物会直接告警
|
|
263
|
+
node bin/osm.js stop
|
|
264
|
+
node bin/osm.js logs # 跟随日志
|
|
265
|
+
node bin/osm.js restart
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
开发模式(server 热重载 + Vite 代理):
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
pnpm dev # API :8787 · Web :5174
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
仓库里的 `bin/osm.js` 与 npm 包里的 `osm` 是同一个脚本,数据目录为仓库的 `.data`:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
node bin/osm.js # 起服务并开浏览器
|
|
278
|
+
node bin/osm.js stop
|
|
279
|
+
node bin/osm.js status
|
|
280
|
+
node bin/osm.js logs
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
`osm` 只起一个进程:Express 同时托管 API 与 `web/dist`,没有第二个端口,也不需要
|
|
284
|
+
另开 Vite。`OSM_HOST` / `OSM_PORT` 覆盖绑定地址和端口,`--no-open` 只起服务不开浏览器。
|
|
285
|
+
pid 与日志落在数据目录的 `osm.{pid,log}`。
|
|
286
|
+
|
|
287
|
+
## API
|
|
288
|
+
|
|
289
|
+
| 端点 | 说明 |
|
|
290
|
+
| --- | --- |
|
|
291
|
+
| `GET /api/status` | 物化状态、水位、各表行数 |
|
|
292
|
+
| `GET /api/sources` | 两库的可用性、原始路径、成本单位(`usd` / `credit`)、行数与物化时间 |
|
|
293
|
+
| `GET /api/top/facets` | 过滤器取值:项目 / 模型 / agent / 工具,加时间跨度与总行数 |
|
|
294
|
+
| `GET /api/top/analytics?from=&to=&project=&model=&agent=` | 当前切片的全部面板数据:totals、KPI 对比、时间桶与模型堆叠、热力图、效率百分位、四张排行表、最重的 200 个会话 |
|
|
295
|
+
| `GET /api/top/sessions?...&q=&order=&dir=&limit=&cursor=` | 会话表:`limit` 个根会话及其子树(默认且上限 500),`cursor` 续上一页;`focus=<id>` 换成该会话的族谱 |
|
|
296
|
+
| `GET /api/top/tool?name=` | 单个工具的调用桶、延迟直方图、agent/项目分布、参数签名、最近错误 |
|
|
297
|
+
| `GET /api/top/session/:id/messages` | 单会话的助手轮次与内容流,外加 `prompts`(用户提问,按时间与轮次交错)——唯一返回正文的端点 |
|
|
298
|
+
| `GET /api/top/search?q=` | 跨会话正文检索,返回命中会话与命中数 |
|
|
299
|
+
| `POST /api/refresh` | 手动重建,`{full:true}` 为全量 |
|
|
300
|
+
|
|
301
|
+
`from` / `to` 是 epoch 毫秒;`from=0` 表示从最早的数据开始。非数字或 `from >= to` 返回 400。
|
|
302
|
+
两库并存:请求头 `x-osm-source: opencode\|qoder` 选库(页眉切换时自动带上),省略则用
|
|
303
|
+
启动时的默认源;未物化的库在第一次被选中时按需建好。`analytics` 的响应与历史长度无关:
|
|
304
|
+
时间轴按列预算取整,会话只带最重的 200 个(全历史约 200KB)。会话表本身用 `cursor`
|
|
305
|
+
翻页,每页大小固定。
|
|
306
|
+
|
|
307
|
+
## 指标定义
|
|
308
|
+
|
|
309
|
+
- **Token 总量**(`total`)= input + output + cache_read + cache_write,**不含 reasoning**。
|
|
310
|
+
推理 Token 单独给出。这套口径与 `opencode-top` 的 `TokenUsage.total` 相同。
|
|
311
|
+
- **Qoder 的 Token 口径**:CLI 不写 input / output / cache_*(恒为 0),唯一可还原的是
|
|
312
|
+
`context_usage_ratio`。`input` 存「本轮累积上下文」= `ratio × 该模型的分母`
|
|
313
|
+
(Qwen 180K / DeepSeek 1M,未知模型回落 200K),其余四类为 0,`total` 即上下文流量;
|
|
314
|
+
图轴写 context 而不是 input。输出 token 不做估算,所以输出曲线恒为 0;生成速度、
|
|
315
|
+
缓存命中这类按输出 / 缓存定义的指标在 Qoder 上没有可用值。压缩事件改在 token 空间
|
|
316
|
+
判定(相邻两轮上下文回落 > 50K tok),不随分母漂移。成本列存积分:整数微积分
|
|
317
|
+
(`credits_micro`,×1e6)是精确源,`cost` 存同一数值以复用现有聚合,面板把
|
|
318
|
+
`$` / `$/Mtok` 换成 `cr` / `cr/Mctx`。
|
|
319
|
+
- **切片口径**(与 `opencode-top` 有意不同):统计「自己的 `created` 落在窗口内,
|
|
320
|
+
且自己的 `project` / `model` / `agent` 命中过滤器」的助手轮次。原版的规则是
|
|
321
|
+
「先选出窗口内活跃过的根会话,再补上它们的全部后代,最后只统计 cutoff 之后的轮次」——
|
|
322
|
+
父会话静默时,挂在它下面的子会话会一并退出统计,而且按 model / agent 过滤在那种规则下
|
|
323
|
+
没有意义。这里的规则是可加的、可交叉过滤的,代价是「一个跨窗口的长会话」在按天看时
|
|
324
|
+
会出现在多个桶里。两套口径的差异见
|
|
325
|
+
[docs/opencode-top-parity.md](https://github.com/kitsuneMori96/open-session-metrics/blob/master/docs/opencode-top-parity.md)。
|
|
326
|
+
- **上下文占用**(`context`)= input + cache_read + cache_write,对整会话求和后除以模型窗口。
|
|
327
|
+
这是一个「上下文流量」指标而不是瞬时占用:长会话会数倍于一个窗口(面板在这种情况下
|
|
328
|
+
显示 `×N`)。`fill` 是另一回事:发送时的 prompt_total / context_window,用于定位压缩触发点。
|
|
329
|
+
- **生成速度**(`output_rate`)= 该会话每个助手轮次 `output / ((completed - created)/1000)` 的
|
|
330
|
+
**中位数**,在物化时算好;耗时缺失(中途休眠导致 `completed` 写到数小时后)的轮次会拉低
|
|
331
|
+
这个值,因此取中位数而不是均值。
|
|
332
|
+
- **重试**(`retry`):同一签名的工具调用在上一次失败后 15 分钟内再次出现。
|
|
333
|
+
连续 ≥3 次同签名调用记为 `loop`。此前仅按签名去重,产生了大量误报(1345 → 28 条)。
|
|
334
|
+
- **代码改动行数**:解析工具调用产生的 unified diff,只统计 hunk 内部的 `+` / `-` 行
|
|
335
|
+
(`Index:` / `---` / `+++` 等头部不计入)。整文件 `write` 按写入行数单独记为 `write_lines`:
|
|
336
|
+
它是一次覆盖而不是增量,两者量纲不同,任何时候都不相加。
|
|
337
|
+
|
|
338
|
+
## 数据表
|
|
339
|
+
|
|
340
|
+
| 表 | 粒度 | 主要字段 |
|
|
341
|
+
| --- | --- | --- |
|
|
342
|
+
| `turns` | 每条消息(`role` = assistant / user) | 各类 Token、成本(Qoder 另有 `credits_micro` 整数微积分)、`fill` / `fill_after`、改动增量与 `write_lines`、预计算的 `bucket` / `day` / `hour`;user 行只带 `text`(正文面板的提问行) |
|
|
343
|
+
| `tools` | 每次工具调用 | 状态、耗时、错误文本、改动与写入行数、调用签名、`target_session`(task 子会话) |
|
|
344
|
+
| `parts` | 每条助手内容 | `kind`(text / reasoning / tool)、`ordinal`、正文或工具入参/回显、`exit_code`、是否截断 |
|
|
345
|
+
| `events` | 每个事件 | 类型、`at` / `end_at`、`count`、`fill`、JSON 详情 |
|
|
346
|
+
| `sessions` | 每会话 | 聚合统计:Token、成本、填充率、`output_rate`、压缩/重试/循环次数、父子关系 |
|
|
347
|
+
| `flow` | 项目 × 5 分钟 | `tokens_gen`、`tokens_cache_read`、成本、主导模型 |
|
|
348
|
+
| `heat` | 项目 × 本地日 × 小时 | Token、成本、轮次数 |
|
|
349
|
+
| `models` | 每模型 | 价目、上下文窗口(models.dev 缓存 + 手工覆盖) |
|
|
350
|
+
| `meta` | — | 水位、物化时间、源库路径、成本单位(`usd` / `credit`) |
|
|
351
|
+
|
|
352
|
+
## 开发
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
pnpm dev # server + web
|
|
356
|
+
pnpm serve # 构建 + 重启 8787(见「快速开始」)
|
|
357
|
+
pnpm typecheck # 两个包 tsc --noEmit
|
|
358
|
+
pnpm test # ETL / Qoder / 数据源 / 不变量测试(离线 fixture,无需服务与网络)
|
|
359
|
+
pnpm -F @osm/server test:smoke # HTTP 冒烟测试(需服务在线)
|
|
360
|
+
pnpm materialize # 增量物化
|
|
361
|
+
pnpm materialize --full # 全量重建(写入临时文件后原子替换)
|
|
362
|
+
pnpm materialize --source qoder --full # 只重建 Qoder 库
|
|
363
|
+
pnpm plans # 按两库实测单价算套餐性价比(见 docs/plan-value.md)
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
前端图表用 `@visx/*`(d3 的无样式原语)与 `@tanstack/react-table` +
|
|
367
|
+
`@tanstack/react-virtual` 自己拼装,没有引入组件库;配色、hairline、等宽微字都在这套
|
|
368
|
+
仓库自己的样式里(`web/src/index.css`)。
|
|
369
|
+
|
|
370
|
+
### 环境变量
|
|
371
|
+
|
|
372
|
+
| 变量 | 默认值 | 说明 |
|
|
373
|
+
| --- | --- | --- |
|
|
374
|
+
| `OSM_SOURCE` | 自动 | 默认数据源:`opencode` 或 `qoder`(页眉可切换,不设则自动探测) |
|
|
375
|
+
| `OSM_SOURCE_DB` | `~/.local/share/opencode/opencode.db` | OpenCode 源库路径(只读) |
|
|
376
|
+
| `OSM_QODER_HOME` | `~/.qoder` | Qoder 数据根目录(读其下 `projects/` 与 `logs/`) |
|
|
377
|
+
| `OSM_DATA_DIR` | `<项目根>/.data` | 两库(`metrics.db` / `metrics-qoder.db`)与价目缓存目录(已 gitignore) |
|
|
378
|
+
| `OSM_HOST` | `127.0.0.1` | API 监听地址 |
|
|
379
|
+
| `OSM_PORT` | `8787` | API 端口 |
|
|
380
|
+
| `OSM_OFFLINE` | — | 设为 `1` 时不访问 models.dev,价目走缓存或零值兜底 |
|
|
381
|
+
|
|
382
|
+
## 实测性能
|
|
383
|
+
|
|
384
|
+
OpenCode 测试数据集:562MB 源库 · 222 会话 · 25,352 轮 · 25,686 次工具调用 · 52,264 条正文。
|
|
385
|
+
|
|
386
|
+
| 项目 | 数值 |
|
|
387
|
+
| --- | --- |
|
|
388
|
+
| 全量物化 | 5.7s(含正文落库) |
|
|
389
|
+
| 增量物化 | 约 100ms |
|
|
390
|
+
| 指标库体积 | 108MB |
|
|
391
|
+
| `analytics` 全历史 | 82ms / 200KB(按天分桶,176 列) |
|
|
392
|
+
| `analytics` 7 天 | 23ms / 18KB |
|
|
393
|
+
| `analytics` 3 天(按小时分桶) | 19ms / 26KB |
|
|
394
|
+
| `facets` | 38ms / 5.5KB |
|
|
395
|
+
| `tool` 明细 | 71ms / 16KB |
|
|
396
|
+
| 会话正文 | 0.1–2.4MB,按需加载 |
|
|
397
|
+
| 会话表(224 个会话一次给全) | 20ms / 169KB |
|
|
398
|
+
|
|
399
|
+
Qoder 的语料是另一套量级:67MB 逐行转录 · 24 个会话(含 8 个子代理转录)· 6,204 轮 ·
|
|
400
|
+
6,765 次工具调用 · 689.965 积分。首次全量物化约 4 秒;之后按会话文件 mtime 增量,
|
|
401
|
+
改一个会话只重建那一个。
|
|
402
|
+
|
|
403
|
+
另用合成十年库(46.6 万轮、40 万次工具调用、618MB)压过同一套查询:全历史
|
|
404
|
+
1.5s(加滚存前 11.8s),响应 177KB;会话分页 93ms(此前 1.4s)。该库由脚本生成,
|
|
405
|
+
不含真实内容,只用来验证「响应体积与历史长度无关」。
|
|
406
|
+
|
|
407
|
+
## 目录结构
|
|
408
|
+
|
|
409
|
+
```
|
|
410
|
+
bin/osm.js # npm 入口:起服务、开浏览器、stop/status/logs
|
|
411
|
+
server/src/
|
|
412
|
+
├── index.ts # 启动、静态托管、水位驱动的刷新循环
|
|
413
|
+
├── api.ts # REST 端点(每库一份,x-osm-source 选库)
|
|
414
|
+
├── config.ts # 路径、端口、时间桶宽度、默认源解析
|
|
415
|
+
├── source.ts # OpenCode 源库 v1/v2 解析(含正文抽取)
|
|
416
|
+
├── source-qoder.ts # Qoder JSONL:按 message.id 合并、子代理并入、积分与上下文
|
|
417
|
+
├── source-select.ts # 按数据源绑定适配器
|
|
418
|
+
├── cli/ # osm materialize / plans
|
|
419
|
+
└── metrics/
|
|
420
|
+
├── schema.ts # 指标库表结构(两库同构)
|
|
421
|
+
├── build.ts # 物化逻辑(轮次指标、正文、事件探针、聚合)
|
|
422
|
+
├── store.ts # 增量编排、原子替换(两库各一份)
|
|
423
|
+
├── analytics.ts # 切片聚合:dashboard / toolDetail / facets
|
|
424
|
+
└── catalog.ts # 模型价目(缓存 + 手工覆盖)
|
|
425
|
+
web/src/
|
|
426
|
+
├── screens/ # Overview / Sessions / SessionDetail / Tools / Compare
|
|
427
|
+
├── components/
|
|
428
|
+
│ ├── Chrome.tsx # 顶栏、切片条、状态栏
|
|
429
|
+
│ ├── DataTable.tsx # 虚拟化表格(排序、树形展开)
|
|
430
|
+
│ ├── Transcript.tsx # 虚拟化正文面板
|
|
431
|
+
│ ├── Palette.tsx # ⌘K 命令面板
|
|
432
|
+
│ └── charts/ # 时间序列、排行条、热力图、散点
|
|
433
|
+
└── lib/ # API 客户端、切片与数据源作用域、格式化、图表工具、正文行模型
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
## 相关文档
|
|
437
|
+
|
|
438
|
+
- [docs/opencode-top-parity.md](https://github.com/kitsuneMori96/open-session-metrics/blob/master/docs/opencode-top-parity.md) — 与 `opencode-top` 的功能对照,
|
|
439
|
+
以及两处有意的口径差异
|
|
440
|
+
- [docs/qoder-source.md](https://github.com/kitsuneMori96/open-session-metrics/blob/master/docs/qoder-source.md) — Qoder CLI 数据源:
|
|
441
|
+
实测证据、解析规则、单位与模型映射、验收门
|
|
442
|
+
- [docs/source-scope.md](https://github.com/kitsuneMori96/open-session-metrics/blob/master/docs/source-scope.md) — 数据源切换:
|
|
443
|
+
作用域定位、页眉线框与像素账、两库契约、切片归属、验收门
|
|
444
|
+
- [docs/backend-refactor.md](https://github.com/kitsuneMori96/open-session-metrics/blob/master/docs/backend-refactor.md) — 2026-10 后端重构的接口变更明细
|
|
445
|
+
- [docs/motion.md](https://github.com/kitsuneMori96/open-session-metrics/blob/master/docs/motion.md) — 动效令牌、六个动词、交互对照与静态审计的规则
|
|
446
|
+
- [docs/markdown-transcript.md](https://github.com/kitsuneMori96/open-session-metrics/blob/master/docs/markdown-transcript.md) — transcript 的 Markdown 子集:
|
|
447
|
+
映射表、语料实测、表格判据、搜索投影、验收门与已知边界
|