🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

硬件监控面板 weihaobin0818-lgtm/dsh-system-monitor

DeepSeek Harnessv1.0.0
weihaobin0818-lgtm23 天前有提交已提供中文文档扫描:含敏感能力

在侧栏实时查看 CPU、内存、磁盘占用并定位到具体应用

30.8/100
综合分
1
GitHub 星标
108
npm 周下载
—/5
用户评分

数据截至 2026/10/12 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-system-monitor
npm 源 · dsh-system-monitor 的发布者账号已校验与本仓库一致

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么[目录]—
dsh 原生插件 · market
暂无时间戳·判据 →
装得上吗[实装验证]✓
本站已在 dsh 0.2.1-rc.2 下真实安装成功
非静态推断
L4 · 真实安装等级说明 →
数据截至 2026/10/27·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/04·判据 →
还在维护吗[仓库]✓
活跃:最近一次提交在 23 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →

可信度证据

安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包dsh-system-monitor @ 1.0.0
✓Node 引擎要求 >=20 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/12 22:43:56

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 18 处,展示前 17 条)
  • 发起网络请求
    client.js:912
    const response = await fetch(url, { credentials: 'same-origin', cache: 'no-store' })
  • 发起网络请求
    client.js:937
    const response = await fetch(target.href, { credentials: 'same-origin', cache: 'no-store' })
  • 发起网络请求
    client.js:972
    const response = await fetch(alertsUrl(), { cache: 'no-store', credentials: 'same-origin' })
  • 发起网络请求
    client.js:1243
    const response = await fetch(alertsUrl(), { cache: 'no-store', credentials: 'same-origin' })
  • 发起网络请求
    client.js:2460
    const response = await fetch(url, { cache: 'no-store', credentials: 'same-origin' })
  • 写入本地文件
    metrics.js:632
    writeFileSync(path, `\uFEFF${source}`, 'utf8')
  • 删除本地文件
    metrics.js:970
    try { rmSync(path, { force: true }) } catch { }
  • 写入本地文件
    metrics.js:1132
    writeFileSync(this.ratePath, String(rate))
  • 写入本地文件
    metrics.js:1193
    writeFileSync(this.ratePath, String(this.samplingRate()))
  • 执行外部命令来自配置/变量
    metrics.js:1207
    child = spawn(
  • 读取本地文件
    metrics.js:1543
    const raw = JSON.parse(readFileSync(statePath(), 'utf8'))
  • 写入本地文件
    metrics.js:1582
    mkdirSync(join(path, '..'), { recursive: true })
  • 写入本地文件
    metrics.js:1583
    writeFileSync(path, JSON.stringify({
  • 写入本地文件
    metrics.js:1638
    mkdirSync(historyDir(), { recursive: true })
  • 读取本地文件
    metrics.js:1657
    names = readdirSync(historyDir())
  • 删除本地文件
    metrics.js:1668
    rmSync(join(historyDir(), name), { force: true })
  • 执行外部命令来自配置/变量
    metrics.js:2157
    child = spawn(
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

社区实装还没有人打卡 · 装过的第一个就是你
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 实时查看CPU内存磁盘
  • 读数下钻到具体应用
  • 容量与内存耗尽预测

DSH 侧栏实时硬件监控:CPU / GPU / 内存 / 磁盘一页看完,每秒刷新,每个读数都能下钻到是哪个应用。支持容量与内存耗尽预测、阈值告警与 NDJSON 导出。纯 Node,零运行时依赖,完整功能仅 Windows。

v1.0.02026-10-12
DeepSeek-Harness 侧栏硬件监控小插件 · 功能 · 一页看完 CPU、GPU、内存、磁盘,每秒刷新 · 按应用归因 —— 每个读数都能下钻到具体进程,带应用图标 · 预测 —— 磁盘写满时间、内存耗尽时间 · 告警 —— 阈值触发时弹 toast、记日志、侧栏徽标提示 · 导出 —— CSV / JSON / NDJSON,走系统「另存为」对话框 · 历史 —— 滚动 NDJSON 落盘,默认保留 7 天 · 平台 完整功能需要 Windows 10 / 11。Linux 和 macOS 上仍可安装,但降级为 Node 原生读数 (CPU、内存、根卷),其余面板会隐藏而不是显示为空。 · 质量 · 197 条断言,全部通过 · 15 个变异测试,全部被捕获 · 零运行时依赖 —— 只用 Node 和系统自带的 PowerShell · 安装 \\\bash dsh plugin install dsh-system-monitor \\\ 详见 README。
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。

README

由 DeepSeek 最新模型翻译生成
dsh-system-monitor DSH 侧栏实时硬件监控 —— CPU、GPU、内存、磁盘一页看完,每个读数都能下钻到具体应用。 独立的社区开源插件,与深度求索不存在隶属、合作、授权或背书关系。 在 DSH 侧栏放置实时硬件监控面板:CPU、GPU、内存、磁盘信息集中在同一页面展示,每秒自动刷新;每项指标均可下钻查看占用来源,附带对应应用原生图标,展示各应用资源占用明细,支持数据导出为 CSV、JSON、NDJSON 格式。 它和任务管理器的区别 任务管理器仅展示瞬时 CPU 占用(例如 80%),无法定位资源占用主体、预估负载持续时长、预判资源耗尽时间。 本插件为 DSH 侧栏插件,无需启动独立程序,在侧栏提供可视化硬件监控面板,同步完成这三项分析。 主要功能 一页看完 CPU、GPU、内存、磁盘四个环形仪表,加趋势图、核心热力图和磁盘列表,每秒刷新一次。支持舒适与紧凑两种密度,小屏也能一屏看完。 按应用归因点任意读数进入明细,看到的不是一堆 PID,而是按应用分组的列表 —— 带应用自己的图标。可展开到单个进程,也可按占用、按增长、按磁盘三种方式排序。 预测基于实测斜率预测磁盘写满时间与内存耗尽时间 —— 不是线性外推,会考虑采样窗口内的实际变化速率;斜率不够显著时宁可不报。 告警超过阈值时弹出提示、写入告警历史、侧栏图标显示徽标。
展开完整文档(README 剩余 1 节)
dsh-system-monitor

DSH 侧栏实时硬件监控 —— CPU、GPU、内存、磁盘一页看完,每个读数都能下钻到具体应用。

独立的社区开源插件,与深度求索不存在隶属、合作、授权或背书关系。

在 DSH 侧栏放置实时硬件监控面板:CPU、GPU、内存、磁盘信息集中在同一页面展示,每秒自动刷新;每项指标均可下钻查看占用来源,附带对应应用原生图标,展示各应用资源占用明细,支持数据导出为 CSV、JSON、NDJSON 格式。

它和任务管理器的区别

任务管理器仅展示瞬时 CPU 占用(例如 80%),无法定位资源占用主体、预估负载持续时长、预判资源耗尽时间。
本插件为 DSH 侧栏插件,无需启动独立程序,在侧栏提供可视化硬件监控面板,同步完成这三项分析。

主要功能

一页看完 CPU、GPU、内存、磁盘四个环形仪表,加趋势图、核心热力图和磁盘列表,每秒刷新一次。支持舒适与紧凑两种密度,小屏也能一屏看完。

按应用归因点任意读数进入明细,看到的不是一堆 PID,而是按应用分组的列表 —— 带应用自己的图标。可展开到单个进程,也可按占用、按增长、按磁盘三种方式排序。

预测基于实测斜率预测磁盘写满时间与内存耗尽时间 —— 不是线性外推,会考虑采样窗口内的实际变化速率;斜率不够显著时宁可不报。

告警超过阈值时弹出提示、写入告警历史、侧栏图标显示徽标。阈值可配,告警需持续一段时间才触发,避免瞬时抖动误报。

导出 CSV / JSON / NDJSON 三种格式,走系统「另存为」对话框,可自选文件夹、改文件名。NDJSON 是推荐的记录格式:追加不重写旧行,加字段不让旧数据失效。

落盘历史滚动写 NDJSON 到磁盘,每天一个文件,默认保留 7 天、上限一年。30 秒批量落盘 —— 直写是每天 8.6 万次文件追加,对没人盯着的监控太浪费。

功能清单:

- 概览 —— 三个环形仪表(CPU / 内存 / GPU)+ 趋势图 + 核心热力图 + 磁盘列表
- 按应用归因 —— 点任意读数进入明细,看是哪些进程、占多少、可展开到单个进程
- 预测 —— 磁盘写满时间、内存耗尽时间,基于实测斜率而不是线性外推
- 告警 —— 超过阈值时弹提示、写入告警历史、侧栏图标显示徽标
- 导出 —— CSV / JSON / NDJSON,走系统「另存为」对话框,可自选目录和文件名
- 历史 —— 滚动写 NDJSON 到磁盘,默认保留 7 天,上限一年
- 自定义时间范围 —— 预设 1 / 5 / 30 分钟,也可输入任意分钟数(最多 3 小时,即内存保留上限)

平台要求

| 系统 | Windows 10 / 11 —— 完整功能需要 |
| DSH | 带 Web 界面的 profile(dsh web) |
| Node | 20 或更高(Harness 运行时已满足) |
| 权限 | 不需要管理员,全部读取当前用户自己会话内的数据 |

Linux 和 macOS 上仍可安装运行,但会降级为 Node 原生读数:CPU、内存和根卷。
GPU、按进程归因、图标、磁盘吞吐都会报告为不可用 —— 对应的面板会隐藏而不是显示为空。

安装

dsh plugin install dsh-system-monitor

或者开发时用 link: 指向本地目录,详见下方 Installing it。

不装驱动、不常驻服务、不联网,也不需要管理员权限。

质量

这个项目自带测试,而且测试被验证过会失败:

| 断言 | 197 条,全部通过 |
| 变异测试 | 15 个,全部被捕获 |
| 运行时依赖 | 0 个 —— 只用 Node 和系统自带的 PowerShell |

变异测试的含义是:故意往代码里注入 15 个 bug,如果测试没抓住任何一个,说明那条测试是假的。能全抓住,才说明这些断言真的会失败。

npm test # 197 条断言
npm run test:mutate # 15 个变异,验证测试本身
npm run release:check # 发布前检查:协议、版权、包内容、隐私信息

关于本文档

以下正文为英文。中文只覆盖到这里 —— 接口、配置项和实现细节请往下读。
配置项的含义在 Configuration 一节,每个都有说明。

DSH 插件包,在 Harness 侧边栏里放一个实时硬件监视器:CPU、GPU、内存和磁盘集中在一页,每秒刷新一次,每个仪表盘背后都有按应用拆分的明细 —— 包括每个应用自己的图标 —— 支持导出 CSV、JSON 和 NDJSON 格式的数据。

社区插件。与 DeepSeek 无隶属关系,未获其背书,也无任何关联。

环境要求

| 平台 | Windows 10 / 11 可获得完整功能 |
| DSH | 带 web 界面的配置档(dsh web) |
| Node | 20 或更高 —— Harness 运行时本身已满足 |
| 权限 | 无;一切都从当前用户自己的会话中读取 |

在 Linux 和 macOS 上,插件包仍可安装并运行,但会降级为只有 Node 原生读数:CPU、内存和根卷。GPU、按进程归因、图标、磁盘吞吐和电池都会报告为不可用,对应的面板会隐藏而不是显示为空。

两半对不上时

每次页面加载,Client 都会重新构建并预留。Host 模块不会 —— 它一直留在 DSH 进程里,直到 DSH 重启。所以开发期间两者会不断错位,而版本不匹配过去会表现为它碰巧弄坏的任何东西:面板消失、导出把窗口刷白、数据悄悄为空。没有任何线索指向版本。

于是 Host 声明一个协议号,Client 拒绝假装:

| metrics.js 里的 SAMPLER_PROTOCOL | 通信契约。只有当载荷的形状发生不兼容变化时才递增 —— 字段改名、单位改变、键被移除。修 bug 不算。 |
| client.js 里的 REQUIRED_PROTOCOL | 这个 Client 是按哪个版本写的。 |
| 不匹配 | 面板顶部出现横幅,列出两个号,提示重启 DSH。 |

Host 完全不报告协议时,按协议 0 处理,而不是当作兼容 —— 把缺失字段读成「没问题」,恰恰会在警告最该出现的时候把它消掉。

采集器本身失败时

采样器起不来不算资源读数,但它是这个插件能报告的最重要的事:一旦发生,所有依赖计数器的面板都会丢数据。它走现有的告警通道,而不是第二个它自带一套机制,所以日志行、toast 和侧边栏徽标都能直接拿到它,不用额外处理。

它只播报一次,不是每个 tick 都播报——重复告警是噪音,而噪音正是真正告警被忽略的原因。它只带一个 code,不带 value,渲染器会省略单位,而不是打印 sampler null%。有样本到达时就会清除它。

按应用统计磁盘吞吐量

与 CPU、内存和 GPU 分开排名,并且用自己的较慢时钟采样——每个进程名一个 PerformanceCounter 并不便宜,而且磁盘归属的变化尺度是分钟级。实测八个进程名大约 11 ms。

读数是计数器的 RawValue,即累计字节数,在 Node 里做差分。这一点很重要:IO Read Bytes/sec 是速率,而每三十秒采样一次的速率描述的是采样那一秒,不是那三十秒。累计计数器在任何采样频率下都正确——网络数据源在被移除之前也教过同样的道理。

计数器回退(PID 被回收、进程重启)时不会产生速率,而不是产生负流量;已退出的进程会被丢弃,这样基线映射在长会话中不会无限增长。

安全模型,直说

这个插件面向单用户机器,它的路由没有认证。

它的四个 HTTP 端点绑定到 loopback,这台机器上任何能访问 127.0.0.1 的东西都能读取它们。它们暴露的内容并不无关紧要:每个运行进程的名称和完整可执行文件路径、卷列表,以及机器的主机名。在共享或多用户主机上,这就是信息泄露,插件内部再怎么小心也改变不了。

这是有意为之,不是疏忽。认证通道意味着用户得管理一个 token,或者一套 RPC 接口把插件绑死在 Harness 的某一种传输方式上——所有用户都要付出这些代价,去防御一种只存在于共享机器上的威胁。所以诚实的做法是说清楚这是什么,让读者自己决定:

- 单用户机器——照现在这样用没问题。
- 共享机器、终端服务器,或任何有其他已登录用户的环境——不要运行它。在同一主机上,loopback 不是用户之间的安全边界。

不会往 ~/.dsh-system-monitor/ 之外写任何东西,不会往任何地方发送数据,也不会请求提权。

它增加了什么

| 位置 | 内容 |
| sidebar.panellist(id system-monitor) | 左侧栏里的仪表盘图标;它的标签就是面板标题和无障碍名称 |
| main(key system-monitor) | 该图标打开的页面 |

概览显示 CPU、内存和 GPU 的环形仪表盘,带 60 秒迷你趋势线,还有每个逻辑核心的柱状图,以及每个卷的已用/可用条。颜色跟随主题:低于 70% 用品牌色,70% 起用警告色,90% 起用错误色。

每张卡片回答的不只是「用了多少」:

- CPU 报告真实时钟速度,由睿频比例推导而来,处理器实际运行的频率,紧挨着标称频率。
- 内存不再只给一个百分比,而是拆成:空闲、待机缓存,以及已提交/上限这一对。后者在 Windows 上最有用——机器可能显示 80 % 物理内存占用,但提交量早已超过 RAM,实际靠页面文件在撑。
- GPU带有按引擎类型拆分的明细,显示在它自己的详情标签页上。

侧边栏图标上的徽章会先变琥珀色,再变红色,只要某个资源持续高于阈值——这样不用打开面板也能看到问题。

可视化

| 元素 | 展示内容 |
| 环形仪表 | 当前 CPU、内存和 GPU,各带 60 秒迷你趋势线,以及与十秒前相比的 ▲/▼ |
| 趋势图 | CPU、内存和 GPU 放在同一条时间线上。悬停会锁定十字准线,并报出那一刻三条序列的值,这正是让尖峰可归因的关键 |
| 每核心柱状图 | 每个逻辑核心的即时负载 |
| 核心热力图 | 同样的核心在 40 秒内的变化,最新一列在最右侧——单个核心跑满在这里一眼可见,柱状图做不到 |
| 内存构成 | 按工作集排前六的应用堆叠成一条,然后是其余应用和空闲内存,带图标图例(内存标签页) |
| 磁盘吞吐 | 读写在同一个刻度上的历史曲线,位于各卷和各物理磁盘行上方(磁盘标签页) |
| GPU 引擎 | 最忙的引擎排最前——3d、copy、video decode、compute(GPU 标签页) |
| 应用表格 | 按应用分列的条形,以展示的最大一行为基准缩放 |

图表配色(热力渐变和构成调色板)刻意不用主题 token:热力渐变需要按严重程度分档固定色相,分类调色板需要色相均匀分隔。两者都使用固定的明度和饱和度,以便在浅色和深色主题下都清晰可读。其余一切都用 --dsw-alias-。

外观与手感

页面遵循 Harness 设计语言,而不是自创一套:扁平卡片,在 --dsw-alias-bg-layer-1 上带 .5px 细边框,--dsw-radius-xl 圆角,页面标题 20-21px/600,卡片标题 14px/600,所有数字都用 tabular-nums,焦点环由 --dsw-focus-ring- 构建。层次感来自分层背景(layer-1 卡片、layer-2 详情面、layer-3 凹槽和轨道),而不是投影,已发布的页面就是这么做的。

每张仪表卡片都带一层淡淡的径向着色,以及一个用自身状态色绘制的资源图标,所以三张卡片不用读文字就能区分。实时指示器只在读数到达时脉动,所有过渡在 prefers-reduced-motion 下都会禁用。

样式表用了 27 个 --dsw- token,全都是已发布的 Harness 页面已经引用过的——没有猜任何 token 名,所以重命名只会让外观降级,而不会破坏渲染。

点击某个仪表——或它的标题按钮——会打开该资源的占用明细 / Usage details,回答「哪个应用在用它?」:
| 标签页 | 行 |
| CPU | 按整机 CPU 占用率排名的应用,旁边显示内存 |
| 内存 | 内存构成条,然后按工作集排名的应用,旁边显示 CPU |
| GPU | 按引擎类型拆分,然后按 GPU 利用率排名的应用 |
| 磁盘 | 吞吐量历史,卷的已用/总量/可用,然后每块物理磁盘的速率 |

三个应用标签页还带一个 按占用 / 按增长 开关:按占用按该标签页的资源排名,按增长按最近几分钟的内存变化排名。选中一行会展开成该应用自己的进程。

行按应用分组,所以十二个 msedge 进程只出现一次,带进程数和一个图标。条形按当前显示的最大行缩放;精确值始终在数字列里。

归因

详情表回答「谁在用」。有两件事走得更远,因为绝对数字会掩盖人们真正关心的两种情况。

增长排名。 Host 每 30 秒采集一次按应用的内存基线,保留十分钟。按增长 / By growth 开关按最长可用窗口内的内存变化给应用排名,所以缓慢泄漏的进程会升到顶部,而不是藏在当前恰好最大的那个下面。基线里不存在的应用标记为新,而不是报告为增长;低于 8 MB 的变化视为噪声;行里会说明实际测量的窗口。在一个 62 秒的观测窗口里,变化最大的是聊天客户端 +42.7 MB、shell +41.2 MB,以及 Harness 自己 −191.0 MB —— 最后这个是它刚释放的内存,任何绝对读数都看不出来。

单个应用详情。 选中任意一行会展开成该应用自己的进程 —— PID、CPU、内存、GPU 和启动时间 —— 外加解析出的可执行文件路径。这就是「Chrome 的九个进程里哪个有问题」得到回答的地方,也是这些表按应用分组的原因。

启动时间来自 Process.StartTime,对受保护进程会抛异常;开发机上 260 个进程里约 141 个能给出启动时间,其余显示为短横线。

历史、导出与持久化

时间范围。 Host 保留三十分钟对齐的采样,头部提供 1 / 5 / 30 分钟。无论窗口多大,载荷都会缩减到 180 个点,所以三十分钟视图和一分钟视图一样只花约 9 KB,超出范围的请求会被钳到最近的可用窗口,而不是直接信任。

会话摘要。 峰值首次累积以来的极值 —— CPU、内存、GPU 和磁盘延迟 —— 每个都带发生时刻和当时该怪哪个应用,外加运行平均值。实时读数回答「现在怎样」;这个回答「一直有多糟」。

导出。 GET …/export?format=csv|json|ndjson&scope=series|apps|full&window=… 回答三个不同的问题,每个问题配以适合它的格式:

| 范围 | 是什么 | 格式 |
| series | 每个样本一行:CPU、内存、GPU、磁盘读/写、延迟、队列 | CSV · JSON · NDJSON |
| apps | 每个应用当前的状态:进程、CPU、内存、GPU、增长、可执行文件路径 | CSV · NDJSON |
| full | 一个包,包含全部读数、能力、配置、告警历史和会话峰值 | JSON |

三者都带 content-disposition: attachment,所以 Status 标签页直接链接到它们。采样率不同的序列会在彼此的行里留下空缺,而不是被重采样成从未测量过的数字。

apps 是时间点快照,不是窗口平均值,因为按应用的历史不保留——只保留当前聚合值和内存增长基线。增长列是例外:它们描述一段真实的时间跨度,并带上所测量的跨度。

为什么 NDJSON 是应该统一采用的格式

format=ndjson 每行写一个完整的 JSON 对象。这是采样器本来就会记录的格式,在你盯着一个不断增长的日志时,它在三个关键属性上胜过 CSV:

| | CSV | NDJSON |
| 追加 | 安全,但表头定义了模式 | 安全,且每行自描述 |
| 新增字段 | 使所有已有文件失效——旧行的形状现在不对了 | 旧行仍然有效;只是缺少该字段 |
| 写入中断 | 一次部分刷盘会损坏该行,而且往往还会破坏其后所有内容的解析 | 只损失一行 |
| 嵌套 | 不可能——应用的可执行文件路径没问题,嵌套对象不行 | 原生支持 |
| 读取 | 各种电子表格 | jq、pandas.read_json(lines=True)、DuckDB read_ndjson_auto() |

最后一行是实用论据。DuckDB 用一条语句就能把一个月的数据从 NDJSON 变成 SQL 或 Parquet,无需导入步骤:

-- query it directly
SELECT date_trunc('minute', to_timestamp(t / 1000)) AS minute, avg(cpu), max(mem)
FROM read_ndjson_auto('system-monitor-series-.ndjson')
GROUP BY 1 ORDER BY 1;

-- or convert the archive to a columnar file
COPY (SELECT * FROM read_ndjson_auto('system-monitor-.ndjson'))
TO 'monitor.parquet' (FORMAT PARQUET);

CSV 仍是默认格式,原因一如既往:人们用 Excel 打开它。JSON 则留给需要单个文档而非多行的情况。

长期历史。 每天一个 NDJSON 文件,放在 ~/.dsh-system-monitor/history/ 下,按三十秒一批追加——如果按每秒一个样本直接写入,一天就是 86,400 次文件追加,而根本没人在看这个监控。

保留期是有意设限的。historyDays 默认 7,上限为一年,清理在每次刷盘时运行,而不是按一个可能撑不过重启的调度。一个悄悄填满磁盘的监控,比一个完全没有历史的监控更糟,所以这里不允许配置出问题:

| historyDays | 效果 |
| 7(默认) | 每秒采样,保留一周,约 60 MB |
| 0 或负数 | 完全关闭历史记录;不缓冲任何数据,更不会写入 |
| 99999 | 上限收敛到 365 |

每行大约 100 字节,一天约 8 MB。这些文件与导出写入的 NDJSON 格式相同,所以一个月的文件只需一次 DuckDB 查询。

持久化。 极值、告警日志和降采样后的图表尾部会写入 ~/.dsh-system-monitor/state.json —— 最多每分钟一次,且仅在峰值或告警真正变化时写入。下次启动时会恢复,因此插件在重启后仍能回答「这一周一直是这样吗」:

startedAt : 2026-10-11T14:56:38Z (this process)
since : 2026-10-11T14:55:14Z (peaks accumulated since — restored) cpu peak : 18.2% at 14:55:52 label=DeepSeek Harness series : 35 points already there before the first sample

设置 persist: false 可关闭。每次读写都有包裹处理:只读的主目录是合法环境,不是错误;保存失败会静默跳过,而不会作为插件故障抛出。

预测

真正会耗尽的只有两样东西,所以只预测两件事:可用磁盘空间和提交内存。两者都每分钟采样一次,形成一小时的序列,并用最小二乘斜率拟合。

设计原则是绝不给数据不支持的预测:

- 只有趋势确实向下、基线跨度至少十分钟、且结果在十年以内时,才预测卷的耗尽时间。
- 只有提交内存确实在上升、且基线跨度至少五分钟时,才预测内存。
- 原始斜率和测量窗口始终会报告,所以平稳趋势读作「已测量,没有变化」,而不是看起来像坏了。卡片会显示 按最近 12 分钟的速度,容量基本不变,而不是什么都不显示。

| 情况 | 结果 |
| 剩余 50 GiB,每天恰好减少 1 GiB,窗口 10 分钟 | daysLeft: 50 |
| 同一卷,平稳 | 斜率 0,daysLeft: null |
| 同一卷,窗口 4 分钟 | 斜率已测量,daysLeft: null |
| 卷在增加空间 | daysLeft: null —— 绝不预测写满 |
| 提交内存 25 分钟增加 1 GiB,余量 9 GiB | secondsLeft: 13500(精确) |
| 提交内存在缩小 | secondsLeft: null |

电池来自 Get-CimInstance Win32_Battery,每十二次采样轮询一次,因为这是慢查询,而电池状态以分钟为尺度变化。它在循环内部探测,而不是在启动时,这样 CIM 不可用的机器不会延迟首次读数。注意 Battery Status 性能计数器集在 Windows 上根本不存在 —— CIM 是唯一途径 —— 且充电时 EstimatedRunTime 报告为零,所以不合理值会被丢弃,而不是显示为「剩余 0 分钟」。

仪表盘布局

这个面板既为墙面显示设计,也为笔记本设计。

密度。 舒适 / Compact 会切换所有关键尺寸 —— 环形仪表 104→84 px,趋势图 168→108 px,每核心柱状条 60→36 px,内边距和卡片间距也更紧凑。紧凑模式能把整个仪表盘塞进 1080p 屏幕;页面必要时仍可滚动,所以不会有内容够不着。选择保存在 localStorage 里,刷新后依然生效。

响应式分栏。 页面最大宽度 1560 px,三个仪表在 240–276 px 之间重排,核心网格和磁盘卡片同处一个 auto-fit 区域:窗口窄时上下堆叠,窗口宽时并排显示。以前它们总是堆叠,宽屏上浪费了大片空间。

趋势图。 它是整个页面的核心,所以承载的信息更多:

- 时间轴 —— 长窗口显示时钟时间,短窗口显示 −30s 这类偏移量,取决于你实际想看哪种;
- 告警区间,而不是单个标记。一次状态转换会设定级别,直到下一次转换,这段时间区间被着色,于是你能看出某个问题持续了多久,而不只是它何时触发;
- 每条序列的峰值标记,这样已经过去的尖峰依然可读,不会看起来像一段平静期。

以上每一项都在渲染测试套件里实测,而不是靠假设:测试框架会统计坐标轴刻度、着色区间和峰值点,并断言两种密度下的圆环、图表和核心柱状条尺寸。

诊断

每次采样都跑四项检查,回答的是「是不是真出了问题」,而不只是「数字是多少」。

资源压力。 每项资源按各自的饱和标准打 0–1 分 —— CPU 90 %、内存 95 %、GPU 95 %、磁盘 50 ms 延迟或队列长度 4 —— 最差的那项会在标题行里被点名为瓶颈。容量被刻意排除在外:90 % 满的磁盘不是慢磁盘。Status 标签页把四项都画成条形,并高亮瓶颈。

CPU 降频。 用实时频率 ÷ 标称频率,但只在 CPU 高于 45 % 时才计算。空闲时频率低是电源管理,不是降频,把它标出来会让这个指标失去意义。超过负载下限后,比值低于 0.85 会在 CPU 卡片上亮起一个标记,同时会话峰值频率保持可见,方便对比 —— 在开发机上就是标称 2.0 GHz 对峰值 4.07 GHz。

磁盘争用。 Avg. Disk sec/Transfer 和 Current Disk Queue Length 与吞吐量一起显示在磁盘卡片上、磁盘标签页里,以及一段延迟历史中。单看吞吐量无法区分一块繁忙的磁盘和一块空闲但搬运同样字节数的磁盘;延迟和队列深度可以。

告警历史。 每次告警级别转换都会连同原因一起记录,所以「这是什么时候开始的」有答案。转换会以垂直标记出现在趋势图上,与图表当时的走势对齐,同时以列表形式出现在 Status 标签页。只记录变化,且日志有上限,所以一个反复抖动的资源不会让它无限增长。

数字是怎么采集的

一切都跑在 Host 那一半里,无论打开多少个页面,都只维持一套采样节奏。
- CPU — 每个逻辑核心的 os.cpus() 时间增量,聚合统计并保留每核心数据。
- 内存 — os.totalmem() / os.freemem()。
- 磁盘 — 对已挂载的盘符执行 fs.statfs,每 20 秒重新扫描一次。
- GPU、时钟频率、内存构成、进程、磁盘吞吐与延迟 — 一个长期运行的 PowerShell 子进程。它读取 WDDM 性能计数器 \GPU Engine()\Utilization Percentage、\GPU Adapter Memory()\{Dedicated,Shared} Usage、
\PhysicalDisk()\{Disk Read Bytes/sec,Disk Write Bytes/sec,Avg. Disk sec/Transfer,Current Disk Queue Length,Disk Transfers/sec}、
单实例计数器 \Processor Information(_Total)\% Processor Performance 和 Processor Frequency、
\Memory\{Available MBytes,Committed Bytes,Commit Limit,Standby Cache Normal Priority Bytes}
以及 \Paging File(_Total)\% Usage,再通过 Get-Process 读取进程表,每次采样输出一行紧凑的 JSON。

启动时会探测每个计数器是否存在,不存在就直接跳过,所以没有 GPU 或页面文件计数器的机器只会降级,不会报错。

这里有两个刻意的选择:

- GPU 利用率取最繁忙的引擎类型,与任务管理器的读数一致。
同一批计数器还能免费拿到每进程 GPU 数据,其实例名里带着所属的 pid。
- 进程表用 Get-Process 读取,因为它只要约 15 ms;而能给出每进程磁盘 I/O 的
\Process()\IO Bytes/sec 计数器,每次采样要花约 1.3 s 的 CPU。监控工具不值得付这个代价,
所以磁盘标签页改为报告每卷和每物理磁盘的吞吐。

进程 CPU 以累计处理器秒数上报,在 Node 里换算成百分比,因为只有 Node 这一侧知道墙钟时间增量。结果保留两位小数:单个进程通常只占多核机器的一小部分,客户端会按应用把这些份额加起来。

子进程是按需启动的:浏览器第一次轮询时启动,20 秒没有请求就停止,所以面板没打开就不花任何开销。失败会如实上报,不会藏起来——面板会说明原因(计数器缺失、采样器已停止等),gpu.diagnostics 里带着解释器、它的 PowerShell 版本和最后一行 stderr。解释器按顺序尝试(pwsh、powershell,然后是绝对回退路径),某个解释器缺失就立刻换下一个,不等重试定时器。

应用图标

详情表格显示每个应用的真实图标,取自它的可执行文件。

- 实时采样器为每个应用标签上报一个可执行文件,选该标签背后最大的进程,因为启动器和崩溃处理器经常共用同一个映像名。
- 详情视图打开时,Host 会启动一个短命的 PowerShell 子进程,对所有还缺图标的应用调用 System.Drawing.Icon.ExtractAssociatedIcon,返回 base64 PNG。结果按内容缓存,从 GET /dsh-system-monitor/icon?key=… 提供,带一天的缓存头。
- 输入以 base64 文本跨进程边界,绝不用 JSON 或原始路径: Windows PowerShell 5.1 解析 JSON 数组的方式与 7 不同,而路径若超出控制台代码页,到达时就会损坏。因此提取器在两者上行为一致。
- 该路由只提供采样器自己缓存过的 key,所以它永远不会变成任意文件读取——未知 key、路径穿越尝试和其他路径一律返回 404。
- 可执行文件不可读的应用(大多是提权进程和系统进程)会得到一个带颜色的首字母,这样每一行都可辨认。

侧边栏告警徽标

metrics.js 里的 ALERT_THRESHOLDS 为每种资源保存一个阈值——CPU 85 %、内存 92 %、GPU 95 %、最繁忙的卷 92 %。读数必须持续高于阈值 ALERT_SUSTAIN_MS(5 秒),徽标才会亮起,所以单次尖峰永远不会触发;读数超过阈值 ALERT_HIGH_MARGIN 后,徽标变红。内存阈值故意设在 Windows 自身缓存会误报的点之上。

图标每 5 秒轮询 GET /dsh-system-monitor/alerts。该路由故意不让实时 PowerShell 子进程保持存活:徽标始终挂载,为它唤醒采样器会全天候消耗 CPU。它报告始终采样的 CPU、内存和磁盘读数,GPU 只在某个面板已经让子进程运行时才加入。面板在仪表上方以横幅显示同样的状态。

传输

Host 注册一个回环 HTTP 前缀 /dsh-system-monitor,带三个端点,全部从缓存快照即时响应:

| 端点 | 用途 |
| GET …/metrics | 概览载荷:当前读数、历史和对齐后的序列 |
| GET …/metrics?detail=1 | 同上,外加逐应用行及其图标 |
| GET …/metrics?window=60\|300\|1800 | 窗口化历史,始终缩减到 180 个点 |
| GET …/icon?key=… | 一张缓存的应用 PNG |
| GET …/alerts | 持续负载徽标状态 |
| GET …/export?format=csv\|json\|ndjson&scope=series\|apps\|full&window=… | 采样、逐应用行,或一个完整包 |

Client 半边用 fetch 轮询它们,解析基于 document.baseURI,所以无论 Web UI 从哪个 host、端口和挂载点提供,它都能跟随,无需配置。

配置

每个可调项都从 loader 行的 config 读取。在配置档的 cordis.patch.yml 里覆盖它——这一层在插件升级后依然存在,不像 bundle 自带的补丁:

- id: dsh-system-monitor
config:
alertMemory: 95
showGpu: false

这里有两个细节很重要,两者都耗费过真实的调试时间:

- id 是行 id,不是包名。它是 bundle 的 cordis.patch.yml 插入的 id。
- 省略 name。 同时带 id 和 name 的补丁条目会重新声明行,Loader 不会对已经认识的 id 再执行一次 insert——于是这条覆盖就被悄悄丢掉了。只有 id 是覆盖;id 加 name 才是插入。

| 键 | 默认值 | 含义 |

| alertCpu / alertMemory / alertGpu / alertDisk | 85 / 92 / 95 / 92 | Percent at which the sidebar badge lights |
| alertSustainSeconds | 5 | How long a reading must stay above its threshold first |
| showGpu | true | Hide the GPU card and series on machines where the counters misbehave |
| showClock | true | Show live CPU clock instead of the nominal one |
| showDiskThroughput | true | Show the disk throughput chart |
| showIcons | true | Extract and show application icons |
| showProcesses | true | Ship the per-application breakdown at all |
| persist | true | Keep extremes and the alert log in ~/.dsh-system-monitor/state.json across restarts |
| idleSamplingSeconds | 20 | Cadence with no viewer for 10 min; 0 stops the sampler instead |
| historyDays | 7 | Days of rolling NDJSON history kept on disk; 0 disables it |

Forecasts are always computed; they surface on the memory and disk cards and on
the Status tab.

Numbers are clamped and wrong types fall back to the default, so a typo can make
a threshold odd but can never disable the sampler or wedge the page. The
effective values are shown on the Status tab, so what actually took effect is
never a guess.

A Config schema is exported when @deepseek-ai/schemastery resolves — which it does for a published npm install, giving the Plugin Manager a real settings form and validation at activation. It does not resolve from a linked development bundle, so the import is guarded and the plugin stays correct either way; the defaults live in resolveConfig rather than only in the schema.

Capabilities and the Status tab

Not every machine exposes every counter. Rather than showing an empty panel, the
plugin asks each capability and hides what it cannot measure:

cpu available · memory available · diskCapacity available · clockSpeed available memoryDetail available · gpu available · processGpu available · processes available diskThroughput available · icons available

The Status tab lists all ten with a reason for any that are unavailable, and
shows the effective config. Reasons cross the wire as codes (no-gpu-counters,
spawn-failed, windows-only, …) that the Client localizes, so the Host never composes UI copy and the plugin can ship in any language. A header strip carries the one-line verdict — All normal, or Above threshold · Memory 94.2% · Top cause Google Chrome — computed by the Host from the same thresholds that drive the badge.

Alerts that leave the panel

A monitor whose only output is a panel is a monitor nobody reads. Transitions are
delivered three ways, in decreasing order of how much is running:

| Channel | Reaches you when |
| Toast in shell.overlay | DSH is open, but you are looking at something else — chat, another panel |
| Log line via ctx.logger | DSH is running at all; this is the only channel that survives having no browser open |
| Sidebar badge | The sidebar is visible |

The toast is a list slot in the frame-wide overlay, so it floats above every
column and is not tied to this plugin's panel. Transitions are detected by
polling alerts rather than pushed, which keeps the Host free of any
client-facing RPC surface; the first poll only establishes a baseline, so opening
a page never produces a toast for a condition that was already true. Toasts carry
their own stylesheet, because the panel's is not mounted when they appear.

Alerts are also evaluated on the sampler's own tick, not only when something
polls — otherwise a threshold crossed with no browser open would go unnoticed, which is exactly the case the log channel exists for.

Sampling cadence

The sampler used to stop after twenty idle seconds, which quietly contradicted
the whole retrospective half of the plugin: the history developed holes precisely
when nobody was watching, and an unattended machine is where problems start.

It now slows down instead of stopping:

| Idle for | Cadence |
| < 15 s | every pass (~1.7 s) |
| 15 s – 10 min | every 4th pass (~4.7 s) |
| > 10 min | idleSamplingSeconds (default 20 s) |

Set idleSamplingSeconds: 0 to restore the old stop-when-idle behaviour; that is
the only setting under which the sampler child is ever killed for idleness.

The rate is handed over as a small file the child re-reads each pass, named per
Host process so two instances cannot fight over it. That avoids the alternative — restarting the child to change its cadence — which would cost a fresh first
sample (and a visibly starting GPU) every time a panel opened.

Verified end to end by measuring the child's own sample timestamps through
export, which is the one endpoint that does not count as a viewer: the gaps run
at ~1.8 s while a panel polls, then switch to ~4.4 s exactly 15 s after the last
one.

Tests

npm test # 95 assertions across four suites
npm run test:mutate # prove the suite can still fail

Plain Node, no framework and no dependencies, because a suite that needs setting
up is a suite that stops being run.

| Suite | Covers |
| manifest.test.mjs | Export targets and published files exist; the loader row, the Client module id and the package name all agree |
| client.test.mjs | Every render path including the first one, all five tabs, chart features, both densities, and the alert toasts |
| host.test.mjs | Config clamping, the forecast regressions, the alert log, the pressure verdict, the sampling cadence and alert delivery |

The Client half is exercised without a browser through a fake module loader and a
fake React. The React stand-in is deliberately faithful in one specific way: when
its hook queue is empty, useState returns the initial value the component
passed, exactly as React does. An earlier version did not, which made "what does
this do before any interaction" untestable — and that is precisely the path that shipped a white-screen bug.

test/mutate.mjs exists because a green suite proves nothing until it has been
seen to go red. It reintroduces six real bugs — an unguarded data.health, a Client id that drifts from the package name, a chart that loses its time axis, a trusted partial payload, a forecast that ignores its minimum baseline, an unbounded alert log — and asserts the suite names each one. All six are caught.

One assertion was rewritten because of it: the time-axis check matched
cls.includes('dsm-chartTicks'), which still matched dsm-chartTicksDisabled.
Substring matching on class names produces a test that cannot fail; those checks
are exact matches now.

The mutation runner reports through a file rather than a pipe, because a confined
environment can refuse to give a child process a pipe — and that failure mode is silent, turning every result into a false "missed".

Files

| File | Role |
| monitor.js | Bundle entry / Host plugin: owns the sampler and the four routes |
| metrics.js | Metric collection, the PowerShell live sampler and the icon extractor |
| client.js | Sidebar icon, overview page, detail tables and the alert toasts |
| cordis.patch.yml | The loader row this bundle inserts |
| icon.svg | Plugin-manager card artwork. The card's text lives in package.json's meta block. |

Installing it

dsh plugin install dsh-system-monitor # once published

For local development the bundle is linked instead, which is what
plugin_manager does with a link: spec:

~/.dsh/profiles/<profile>/package.json
"dependencies": { "dsh-system-monitor": "link:/path/to/system-monitor" }

Two Loader behaviours worth knowing

Both cost real debugging time, and neither is obvious from the outside.

A row id is inserted once. If the Loader already knows an entry id, an
insert for that id is not re-applied — so editing the name in cordis.patch.yml and reinstalling silently leaves the entry bound to the old module. Renaming the package means renaming the row id too.

A failed import is remembered. After an entry fails to import once, that
module name stays poisoned for the life of the process: later installs of a
package with the same name keep reporting failed to import even when the
package resolves correctly from the profile directory. Fixing whatever was
actually wrong is therefore not enough — the name has to change, or the application has to restart.

The Client half has neither problem: it is served under a content revision and
its watcher picks up edits, so a page reload is enough there.

Renaming, and releasing

The name is not load-bearing, but it appears in exactly three places, and two
of them are silent when they disagree:

| # | File | What |
| 1 | package.json | name |
| 2 | cordis.patch.yml | the row id and its name |
| 3 | client.js | the id in window.__ModuleLoader__.load({ … }) |

npm test fails on any disagreement: manifest.test.mjs asserts that the package
name, the loader row name and the Client module id are all the same string. That
is not decoration — a drifted Client id means the sidebar icon and the panel never register, with no error anywhere.

Renaming in an already-running Harness additionally needs a restart, because the
Loader remembers a failed import for the life of the process.

Before pushing:

npm test # assertions across four suites
npm run test:mutate # injected bugs, all must be caught
npm run release:check # blocks on missing attribution, stray files, private data

release:check is a separate command rather than part of npm test because it
is expected to fail until the release metadata is decided. It currently reports
two blocking items: the placeholder copyright holder in LICENSE, and the absent
repository field. It also scans every tracked file for machine-specific paths
and personal data — that check exists because an early draft of this README quoted the author's own running applications by name.
预览分享卡片长什么样 ↓weihaobin0818-lgtm/dsh-system-monitor
分享 · DeepSeek Harness Hub
weihaobin0818-lgtm/dsh-system-monitor

在侧栏实时查看 CPU、内存、磁盘占用并定位到具体应用

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部