dsh-client-ui-sidebar-perfmon
Đã xác minh@xmwengxing/dsh-client-ui-sidebar-perfmon · v0.4.2 · MIT · Giao diện web
Real-time host performance monitor for the DeepSeek Harness right Sidebar: CPU / memory / swap gauges, a GPU clock and VRAM line, CPU / GPU / motherboard / drive temperatures, plus a sortable process table — on Linux, macOS and Windows. · DSH 右侧栏实时性能监控:CP
Cài đặt
dsh plugin add @xmwengxing/dsh-client-ui-sidebar-perfmon Xác nhận layer đã áp bằng dsh --profile default --dump-config — xem hướng dẫn cài plugin.
Mã nguồn
Thẻ
Tác giả
Readme
@xmwengxing/dsh-client-ui-sidebar-perfmon
为 DeepSeek Harness 右侧栏提供实时主机性能监控: CPU / 内存 / 交换内存仪表盘、CPU / 显卡 / 主板 / 硬盘 温度,以及可按 CPU、内存、进程名排序的进程列表。
English | 中文
┌─ 资源占用 ────────────────────── G2 · x64 · 已运行 5d 3h ─┐
│ ◜◝ ◜◝ ◜◝ │
│ 12.5% 39.1% 4.9% │
│ CPU 内存 交换内存 │
│ 4 核 · 负载 1.52 4.5 GB / 11.4 GB 0.4 GB / 8.0 GB │
├─ 温度 ─────────────────────────────── °C · hwmon ────────┤
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 58.0°C │ │ 47.0°C │ │
│ │ CPU │ │ 显卡 │ │
│ └──────────────┘ └──────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 35.0°C │ │ 40.0°C │ │
│ │ 主板 │ │ 硬盘 │ │
│ └──────────────┘ └──────────────┘ │
├─ 进程列表 ───────────────────── 共 349 个进程 · 显示 60 行 ┤
│ CPU ↓ 内存 进程名 │
│ 筛选进程名或 PID ────────────────────────────────────── │
│ 99.9% 849 MB · 7% gnome-shell │
│ ▬▬▬▬ ▬▬ PID 147112 · 26 线程 · 运行 │
│ 35.0% 100 MB · 1% msedge │
│ ▬▬ ▬ PID 740756 · 14 线程 · 睡眠 │
└─────────────── 更新于 15:50:20 · 每 2 秒自动刷新 · 立即刷新 ┘
功能
插件向右侧栏贡献一个页面类型,并向会话顶栏贡献一个按钮:
- 性能监控页面 —— 同一时钟驱动的三个窗口:
- 资源占用:CPU、内存、交换内存三个环形仪表,各自显示百分比、一行辅助信息 (核心数与负载 / 已用与总量 / 本机未启用交换内存),标题栏显示主机名。
- 显卡(仪表下方的一条横条,分左右两半):左边核心频率、右边显存占用,各自带一条 表示进度或占比的填充。悬浮会显示显卡型号、最大频率与显存百分比。这一条只占资源卡片 一行,且显存无需任何第三方软件,见显卡频率与显存。
- 温度:CPU、显卡、主板、硬盘四块磁贴,每块两行——摄氏度读数,然后是部件名称。
磁贴按「正常 / 偏热 / 过热」(70 °C 与 85 °C)着色。悬浮在磁贴上会列出它覆盖的
每一个传感器,并在该部件有多个传感器时给出区间;头条取最高读数,因此多核封装
或双硬盘既不会撑宽卡片也不会看不清。任何数据源都答不出的部件显示
—,其悬浮提示 会说明原因,绝不用 0 顶替。该卡片按自己更保守的节奏刷新,见温度。 - 进程列表:每行一个进程,显示 CPU 占用、常驻内存及其占物理内存的比例,
支持按进程名或 PID 筛选。三个标签就是列表的三列表头——
CPU、内存、进程名从左到右,各自正对它所排序的那一列。CPU与内存按该资源从高到低排列整机 进程;进程名切换为按名称排序。两个数值列宽度固定、永不压缩,名称列占用剩余宽度 并截断,因此侧边栏变窄时先牺牲进程名而不是数值,列表也永远不会出现横向滚动条。 列与行之间都有细线分隔;CPU与内存右侧的分隔线同时是拖拽把手——拖动即可 调整该列宽度,双击复位,也可聚焦后用方向键(Shift 加速、Home 复位)。宽度按浏览器记忆。
- 性能监控信息按钮 —— 位于会话顶栏的工具栏末位,紧邻右侧栏自身的展开按钮, 点击即打开(或聚焦)该页面。
三个窗口都按宿主上报的间隔刷新(默认 2 秒)。浏览器标签页隐藏时暂停轮询, 重新可见时立即拉取一次新数据。温度卡片是例外:它按自己更保守的节奏刷新, 因为温度不是累计计数器,而且在 Windows 上读取开销较大。
环境要求
- Linux / macOS / Windows。每个平台各有专用读取器;各平台能提供哪些指标见 平台支持。
- DeepSeek Harness
0.2.0-rc.1或兼容版本。插件注册到右侧栏的 tab 类型注册表 与会话顶栏的工具栏席位,因此需要启动@deepseek-ai/dsh-web-app的 profile (webprofile 即是)。
无运行时依赖:宿主半边只用 node:os 与各平台自带工具,浏览器半边只用 GUI 已提供的 React。
安装
# 从 npm 安装
dsh plugin --profile web add @xmwengxing/dsh-client-ui-sidebar-perfmon
# 从本地检出安装
dsh plugin --profile web add /path/to/dsh-client-ui-sidebar-perfmon
# 从 git 安装(需要先允许 pnpm 执行构建脚本)
dsh plugin --profile web add github:xmwengxing/dsh-client-ui-sidebar-perfmon
然后重启 GUI。用 pm2 管理时:
pm2 restart deepseek-harness-webui
重启前可以先确认插件层已合成:
dsh --profile web --dump-config | grep -A2 perfmon
卸载:dsh plugin --profile web remove @xmwengxing/dsh-client-ui-sidebar-perfmon。
入口在哪里
打开右侧栏,引导页会列出 性能监控 卡片,点击即以 tab 形式打开该页面。 此后会话顶栏的 性能监控信息 按钮可打开或聚焦同一 tab;该 tab 与其它页面一样 支持分栏、拖动与浮出。
配置
默认值刻意保守:每两秒遍历一次 /proc,多个面板共享同一次读取结果。
需要调整时在 profile patch 中覆盖:
# ~/.dsh/profiles/web/cordis.patch.yml
- id: perfmon
name: '@xmwengxing/dsh-client-ui-sidebar-perfmon'
config:
refreshIntervalMs: 1000 # 500–60000,默认 Linux 2000 / macOS 3000 / Windows 4000
processLimit: 100 # 5–500,单次返回的进程行数
cacheMillis: 800 # 0–10000,该窗口内的轮询共享同一次读取
sampleMillis: 150 # 0–2000,首次读数的预热采样时长
projectDirEntryBudget: 50000 # 100–1000000,单个目录一次扫描的条目上限
projectDirMaxDirs: 12 # 1–100,一次扫描覆盖的 distinct 目录数
temperatureIntervalMs: 15000 # 2000–600000,一次温度读数的复用时长
gpuIntervalMs: 4000 # 500–60000,默认跟随 refreshIntervalMs
# gpu: false # 完全隐藏显卡行
# temperature: false # 完全隐藏温度卡片
# projectDir: /srv/demo # 固定扫描某个目录,而不是跟随当前查看的会话
# projectDir: '' # 或完全隐藏目录大小行
平台支持
每个平台族一个读取器,各自读该平台自己的数据源。
| Linux | macOS | Windows | |
|---|---|---|---|
| CPU 占用 / 单核 | /proc/stat |
os.cpus() |
os.cpus() |
| 负载 | 有 | 有 | 无——Windows 没有这个概念,该段直接省略 |
| 内存 总量/已用/可用 | /proc/meminfo(MemAvailable) |
vm_stat(free + 可回收页) |
Win32_OperatingSystem + AvailableBytes |
| 缓存 / 缓冲 | Cached / Buffers |
文件页作为缓存;无缓冲项 | CacheBytes;无缓冲项 |
| 交换内存 | /proc/meminfo |
sysctl vm.swapusage |
页面文件(SizeStoredInPagingFiles) |
| 进程列表 | /proc/<pid>/stat,进程内读取 |
ps -Ao pid=,state=,time=,rss=,comm= |
一次 PowerShell 调用 |
| 进程状态 | 有 | 有 | 无——显示为 — |
| 线程数 | 有 | 无——BSD ps 没有可移植的线程数字段 |
有 |
| 温度 | /sys/class/hwmon,无则 /sys/class/thermal |
powermetrics(需 root)、osx-cpu-temp、istats |
LibreHardwareMonitor / OpenHardwareMonitor、ACPI 热区、存储可靠性计数器、nvidia-smi |
| 项目目录大小 | 进程内遍历,三个平台一致 | 同左 | 同左 |
平台无法提供的字段一律上报 null 并在面板显示 —,同时说明原因:温度磁贴的原因在
它自己的悬浮提示里,其余告警挂在底部时间戳的悬浮提示上。
任何字段都不会用 0 顶替——因为 0 看起来像一次真实测量。
开销:Linux 全部在进程内读 /proc,不启动任何子进程;macOS 与 Windows 每次采样
启动一个辅助进程,因此默认刷新间隔更保守——分别是 3 秒与 4 秒(Linux 为 2 秒),
可用 refreshIntervalMs 覆盖。
没有专用读取器的平台(FreeBSD、Solaris 等)回退到只用 node:os 的读取器:
真实的 CPU 读数与内存总量,交换内存与进程列表标记为不可用,而不是猜测。
数字是怎么来的
所有百分比都是同一累计计数器的两次采样之差,因此插件从不假设时钟节拍:
| 指标 | 规则 |
|---|---|
| CPU 占用 | 两次采样之间,忙时间占流逝时间的比例。 |
| 单核占用 | 同一规则,逐核心计算。 |
| 内存占用 | 已用 / 总量,其中“已用”指“不可用”——页缓存可回收,因此计入可用而非已用。 |
| 交换内存占用 | 已用 / 总量。 |
| 进程 CPU | 同一区间内该进程 CPU 时间的增量,按 100% = 一个核心 归一化——与 top 同一约定,因此 4 核机器上的 8 线程进程可能超过 100%。 |
| 进程内存 | 常驻集大小占总内存的比例。 |
读取器唯一必须遵守的规则是单位自洽:进程 CPU 时间必须与该读取器自己的 CPU 总量
同单位。Linux 两边都是 jiffies,macOS 与 Windows 两边都是毫秒——于是
忙Δ / 总Δ 与 进程Δ / 总Δ × 核心数 把单位约掉,任何地方都不需要假设 USER_HZ。
每次会话的第一次读数会用一次短采样预热,因此面板首帧显示的就是真实百分比而非 0。
有两种状态会如实呈现,而不是显示为 0:
- 本窗口内新出现的进程没有可作差的上一帧,其 CPU 单元格在下一次轮询前显示
—。 - 未启用交换内存的主机显示“未启用”,而不是一个 0% 的空环。
仪表盘下方的项目目录行按需统计:目录遍历是 CPU 与 IO 密集型工作——
停在用户主目录的会话可能有几十万条目——因此面板绝不隐式启动扫描。该行自带
一个按钮,统计当前正在查看的会话的工作目录:浏览器在启动请求里携带会话 id,
宿主经活跃会话存储解析 session.header.cwd;GUI 侧栏里“正在查看”的会话大多是
冷会话(其归属进程从未把它进入本进程的活跃存储),因此宿主会退回到
session-query 服务读取冷记录的 cwd。扫描进行中按钮变为“停止”,且停止落点在
单次文件系统调用之内,已统计的部分结果会带“已停止”说明保留显示。扫描完成后的
读数一直保留到下次扫描替换为止。常规轮询只读存储结果——面板开着在两次扫描之间
零开销。扫描有上限(单目录 5 万条目、单次 12 个目录)、符号链接既不跟随也不计入,
每一处省略都在数字旁说明,而不是默默夸大准确性。
显卡频率与显存
显卡行刻意是不对称的:显存几乎总能读到,频率常常读不到,面板会说明哪个是哪个, 而不是编一个数字出来。
| 读数 | Linux | macOS | Windows |
|---|---|---|---|
| 显存 已用/总量 | amdgpu 的 sysfs(mem_info_vram_*);nvidia-smi |
仅 nvidia-smi |
系统自带的 GPUPerformanceCounters 内存计数器(任何厂商都行,无需安装任何软件),加上注册表中 64 位的适配器总量;有 nvidia-smi 时用它的 |
| 核心频率 | gt_cur_freq_mhz(i915)或 amdgpu 的 pp_dpm_sclk;nvidia-smi |
仅 nvidia-smi |
nvidia-smi(NVIDIA),或运行中的 LibreHardwareMonitor / OpenHardwareMonitor(其它厂商) |
由上表得出两条规则:
- 显存不需要任何第三方软件。 Windows 自身就会为 NVIDIA、AMD、Intel 报告专用显存
占用,而真实的 64 位总量来自显示类注册表键——刻意不用
Win32_VideoController.AdapterRAM,那是 32 位字段,超过 4 GiB 会回绕(本机这块 11 GiB 的卡在那里报的是 4293918720 字节)。 - Windows 没有系统级的频率来源。 计数器集合只有显存占用与利用率,没有频率字段,
因此一台既没有
nvidia-smi也没有硬件监控软件的机器,频率显示—而显存照常读数。 用猜出来的最大频率去编一个百分比,正是本插件在别处一直避免的那种「看起来确定、 其实没有意义」的数字。
布局是一条横条,不是圆环。 一行分成左右两半——左边频率、右边显存——每半是「标签 + 进度条」,填充宽度表示该读数走到了哪一步。刻意不用同心圆环:频率和字节数没有共同的 刻度,两个同心圆弧会暗示存在一个并不存在的刻度。两个填充也诚实地表达了它们不是一回事: 频率的填充是相对它自己的加速上限(一个会浮动的目标,绝不表示「已满百分之多少」), 显存的填充才是真正的「已用/总量」。
它换行,而不是溢出。 每一半声明自己需要的宽度;侧栏正常时两半共处一行,面板窄到 放不下两半时,第二半换到自己的一行并占满整宽。早先的版本完全没有布局,裸文字因此从 卡片左边缘漏了出去——而在加上换行之前,窄面板只会把数值截断。现在既不会截断,也不会 越出卡片边界。
它复用面板自身的刷新节奏(gpuIntervalMs,默认跟随 refreshIntervalMs),
因为加载模型时显存才是关键指标。设 gpu: false 可隐藏该行。
温度
温度是本插件里唯一不做差值的读数——它是绝对值,没有可作差的对象——也是各平台 数据源差异最大的一个。由此产生两个设计后果。
它按自己的节奏读取。 Windows 上一次读取要花一次 PowerShell 调用、一秒以上
(存储可靠性计数器占大头;开发机上实测约 1.6–2.8 秒),每两秒重复一次是荒谬的。
宿主把一次读数复用 temperatureIntervalMs(默认 15 秒),缓存命中时立即返回,
只有第一次读取会让调用方等待(这样面板首帧就有真实数字),过期的读数在轮询
背后刷新而不是挡在它前面。多个面板共享同一次读取。
归属刻意保守,因为数据源并不等价。 各平台的候选源,以及每个源允许填充的部件:
| 部件 | Linux | macOS | Windows |
|---|---|---|---|
| CPU | coretemp / k10temp / zenpower / cpu_thermal 等 hwmon 芯片;x86_pkg_temp 热区 |
powermetrics CPU die;osx-cpu-temp;istats |
LibreHardwareMonitor / OpenHardwareMonitor 的 /intelcpu/、/amdcpu/ |
| 显卡 | amdgpu / radeon / nouveau / i915 / xe 芯片 |
powermetrics GPU die;istats |
监控软件的 /nvidiagpu/、/atigpu/;否则 nvidia-smi |
| 主板 | acpitz、it87、nct6775、dell_smm、thinkpad 等 |
— | ACPI 热区;监控软件的 /lpc/ |
| 硬盘 | nvme、drivetemp 芯片 |
— | 每块物理盘的 Get-StorageReliabilityCounter |
有三条规则值得明确写出,因为每一条都是本功能很容易犯的错:
- Windows 的 ACPI 热区一律归为主板,绝不归为 CPU。 按 ACPI 自身的定义它就是 板级传感器,而且在许多桌面主板上它是一个近乎恒定的占位值:开发机在全核满载 期间始终报告 27.9 °C。把它当作 CPU 温度发布,等于给出一个永远不会动的、 看起来很确定的数字。
- Windows 上 CPU 温度需要硬件监控软件。 没有运行 LibreHardwareMonitor 或
OpenHardwareMonitor 时,普通权限下确实没有可用来源,因此 CPU 磁贴显示
—并说明原因,而不是拿别的部件的读数顶替。请装 0.9.4,不要装最新版——0.9.6 移除了本插件所读取的 WMI 提供程序。0.9.4 是免安装 zip(LibreHardwareMonitor-net472.zip), 解压后以管理员身份运行即可,面板会自动识别,无需任何配置。Intel 的Distance to TjMax是热余量而非温度,已被排除——若计入,CPU 越忙反而显示越低。 - macOS 上普通权限进程读不到 SMC。
powermetrics需要 root;osx-cpu-temp与istats需要用户自行安装。三者都没有时,四个磁贴全是———这才是诚实的答案。
无法识别的传感器绝不会被猜进某个分类:未知的 hwmon 芯片、或硬件监控软件报出的
内存(/ram/)传感器,都会被略过,而不是被错误标成 CPU。硬盘可靠性计数器返回 0
时视为「无传感器」,而不是一块冰点硬盘。
接入方式
一个 bundle,两半:
- 宿主半边(
lib/index.js)在 Connection 共享且已鉴权的/api通道上注册一条 精确路由 ——POST /api/perfmon.snapshot—— 这正是官方 deliverables 与 session-log-export 包使用的同一接缝。注册在那里意味着该路由继承 GUI 的 Host/Origin 防护与浏览器会话鉴权,因此浏览器用同源fetch()即可访问, 插件自身不处理任何凭据。宿主侧带一个短缓存,多个面板共享同一次/proc遍历。 - 浏览器半边(
client/client.js)注册三个界面,全部是公开扩展席位:ctx.sidebarRightTabs.register()——perfmon页面类型及其引导页入口。ctx.slots.register(),席位sidebar.right.pane.tab,以该类型自身的id为键 —— 面板正文。ctx.slots.register(),席位conversation.session.header.utilities—— 顶栏按钮, 排序值最大,因此落在右侧栏展开按钮旁。
服务解析刻意延后:浏览器半边不声明 inject 列表,而是通过 ctx.inject([...])
逐个获取服务,因此缺少右侧栏或 Connection 的 profile 只会跳过对应贡献,
而不会让整个插件一直 pending。
样式只使用共享的 --dsw-* 设计令牌(字面色仅作为 var() 回退出现),
面板自带中英文案并按文档语言选择,因此不依赖 locale 服务。
隐私
插件读取本机内核计数器,只返回给发起请求的浏览器。它自身不发起任何网络请求, 不外发数据,不写文件。路由受与 GUI 其余部分相同的鉴权保护。
已知限制
- 没有进程属主、命令行与进程树。每行包含进程名、PID、状态、线程数、CPU 与 RSS。
- 温度取决于平台暴露了什么。Windows 的 CPU 温度需要运行硬件监控软件
(LibreHardwareMonitor 0.9.4 或 OpenHardwareMonitor,需以管理员身份运行);macOS 需要以 root 运行
powermetrics或安装社区工具;读不到传感器的机器显示—并说明原因。 各部件的确切来源见温度。 - 磁贴里没有风扇转速、电压或逐核心温度明细。每个部件的头条是它最高的那个传感器, 所有传感器都列在悬浮提示里,但没有单独的图表或表格。
- macOS 与 Windows 部分仅有解析器测试。这两个平台的读取器由「抓取的工具输出样本 + 注入的失败路径」覆盖,但作者未在真实 macOS / Windows 硬件上运行过。Windows 的进程状态、 macOS 的线程数是平台本身不提供,而非实现遗漏。Windows 的温度数据源确实在真实硬件上 验证过——上面那条 ACPI 热区的实测结论就来自那里。
- 没有历史数据。面板只显示当前读数,没有迷你趋势图或留存采样。
- 轮询而非推流。刷新是按间隔请求,而非服务端推送。
- 顶栏按钮只负责打开,不负责切换。关闭面板由右侧栏自身的控件完成。
开发
npm install
npm run build # 生成 lib/index.js 与 client/client.js
npm run watch # 改动即重建
npm test # 先构建两半,再跑全部用例
npm test 覆盖:以手工构造的采样验证差值算法;各平台解析器对抓取的工具输出
(包括各种别扭形状:被截断的 ps 时间、含空格的路径、null 计数器、
本应是数组却只有一个对象);通过注入的命令运行器验证各读取器的失败路径;
各平台温度源的归属规则(Windows 的 ACPI 热区绝不会发布为 CPU、计数器返回 0 的硬盘
不是 0 °C 的硬盘、Number(null) 绝不能变成 -273.15 °C)以及温度探测器的缓存 /
过期刷新 / 并发共享;通过 react-test-renderer 验证面板行为(仪表、温度磁贴、标签、
排序、筛选、刷新、错误);已发布 bundle 的注册内容;以及一组契约时效性用例——
它们重新读取已安装的 dsh 包,确认本插件依赖的席位名、服务名与路由规则仍然成立。
另有一个通过 DevTools 协议驱动真实 Chromium 的浏览器验证脚本:
# 另起一个实例,避免影响正在运行的 GUI
dsh --profile web --port 3099 --no-open
# 在 Chromium 监听 9222 的前提下
node scripts/verify-ui.mjs "http://127.0.0.1:3099/?token=<token>" /tmp/panel.png
许可证
MIT