dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
DSH 侧栏实时硬件监控:CPU / GPU / 内存 / 磁盘一页看完,每秒刷新,每个读数都能下钻到是哪个应用。支持容量与内存耗尽预测、阈值告警与 NDJSON 导出。纯 Node,零运行时依赖,完整功能仅 Windows。
DSH 侧栏实时硬件监控 —— CPU、GPU、内存、磁盘一页看完,每个读数都能下钻到具体应用。
独立的社区开源插件,与深度求索不存在隶属、合作、授权或背书关系。
在 DSH 侧栏放置实时硬件监控面板:CPU、GPU、内存、磁盘信息集中在同一页面展示,每秒自动刷新;每项指标均可下钻查看占用来源,附带对应应用原生图标,展示各应用资源占用明细,支持数据导出为 CSV、JSON、NDJSON 格式。
任务管理器仅展示瞬时 CPU 占用(例如 80%),无法定位资源占用主体、预估负载持续时长、预判资源耗尽时间。 本插件为 DSH 侧栏插件,无需启动独立程序,在侧栏提供可视化硬件监控面板,同步完成这三项分析。
一页看完CPU、GPU、内存、磁盘四个环形仪表,加趋势图、核心热力图和磁盘列表,每秒刷新一次。支持舒适与紧凑两种密度,小屏也能一屏看完。 |
按应用归因点任意读数进入明细,看到的不是一堆 PID,而是按应用分组的列表 —— 带应用自己的图标。可展开到单个进程,也可按占用、按增长、按磁盘三种方式排序。 |
预测基于实测斜率预测磁盘写满时间与内存耗尽时间 —— 不是线性外推,会考虑采样窗口内的实际变化速率;斜率不够显著时宁可不报。 |
告警超过阈值时弹出提示、写入告警历史、侧栏图标显示徽标。阈值可配,告警需持续一段时间才触发,避免瞬时抖动误报。 |
导出CSV / JSON / NDJSON 三种格式,走系统「另存为」对话框,可自选文件夹、改文件名。NDJSON 是推荐的记录格式:追加不重写旧行,加字段不让旧数据失效。 |
落盘历史滚动写 NDJSON 到磁盘,每天一个文件,默认保留 7 天、上限一年。30 秒批量落盘 —— 直写是每天 8.6 万次文件追加,对没人盯着的监控太浪费。 |
功能清单:
| 系统 | 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 一节,每个都有说明。
A DSH bundle that puts a live hardware monitor in the Harness sidebar: CPU, GPU, memory and disk on one page, refreshed once a second, with a per-application breakdown — including each application's own icon — behind every gauge, supporting data export in CSV, JSON and NDJSON formats.
A community plugin. Not affiliated with, endorsed by, or connected to DeepSeek.
| Platform | Windows 10 / 11 for the full feature set |
| DSH | a profile with the web surface (dsh web) |
| Node | 20 or newer — the Harness runtime already qualifies |
| Privileges | none; everything is read from the current user's own session |
On Linux and macOS the bundle still installs and runs, but degrades to the Node-native readings only: CPU, memory and the root volume. GPU, per-process attribution, icons, disk throughput and battery all report as unavailable, and their panels are hidden rather than shown empty.
The Client is rebuilt and reserved every time the page loads. The Host module is not — it lives in the DSH process until DSH is restarted. So during development the two drift apart constantly, and a mismatch used to present itself as whatever it happened to break: a panel that vanished, an export that blanked the window, data that was quietly empty. Nothing pointed at the version.
So the Host declares a protocol number, and the Client refuses to pretend:
SAMPLER_PROTOCOL in metrics.js | The wire contract. Bumped only when the payload's shape changes incompatibly — a renamed field, a changed unit, a removed key. Not for a bug fix. |
REQUIRED_PROTOCOL in client.js | What this Client was written against. |
| Mismatch | A banner at the top of the panel naming both numbers, saying to restart DSH. |
A Host reporting no protocol at all is treated as protocol 0, not as compatible — reading a missing field as "fine" would silence the warning in exactly the case it exists for.
A sampler that cannot start is not a resource reading, but it is the most important thing this plugin can report: every counter-backed panel loses its data when it happens. It rides the existing alert channel rather than a second mechanism of its own, so the log line, the toast and the sidebar badge all pick it up for free.
It is announced once, not every tick — a repeating alert is noise, and noise
is how a real alert gets ignored. It carries a code and no value, and the
renderers leave the unit off rather than printing sampler null%. A sample
arriving clears it.
Ranked separately from CPU, memory and GPU, and sampled on its own slower clock —
a PerformanceCounter per process name is not free, and disk attribution changes
on a scale of minutes. Measured at about 11 ms for eight names.
The reading is the counter's RawValue, which is cumulative bytes, differenced
in Node. This matters: IO Read Bytes/sec is a rate, and a rate sampled once
every thirty seconds describes the one second it was sampled in, not the thirty.
Cumulative counters are correct at any cadence — the same lesson the network
source taught before it was removed.
A counter that goes backwards (a recycled PID, a restarted process) yields no rate rather than negative traffic, and exited processes are dropped so the baseline map does not grow without bound over a long session.
This plugin is for a single-user machine and its routes are not authenticated.
Its four HTTP endpoints bind to loopback, and anything that can reach
127.0.0.1 on this machine can read them. What they expose is not trivial: the
name and full executable path of every running process, the list of volumes, and
the machine's hostname. On a shared or multi-user host that is an information
disclosure, and no amount of care inside the plugin changes it.
The choice was deliberate rather than an oversight. An authenticated channel would mean a token the user has to manage, or an RPC surface that ties the plugin to one Harness transport — costs paid by every user to defend against a threat that only exists on a machine they share. So the honest thing is to say what this is, and let the reader decide:
Nothing is written outside ~/.dsh-system-monitor/, nothing is sent anywhere,
and no elevated privileges are requested.
| Where | What |
|---|---|
sidebar.panellist (id system-monitor) | The gauge icon in the left rail; its label is the panel title and the accessible name |
main (key system-monitor) | The page that icon opens |
The overview shows ring gauges with 60-second sparklines for CPU, memory and GPU, a per-logical-core column chart, and a used/free bar per volume. Colour follows the theme: brand below 70 %, warn from 70 %, error from 90 %.
Each card answers more than "how much":
A badge on the sidebar icon turns amber, then red, when a resource stays above its threshold — so a problem is visible without opening the panel at all.
| Element | What it shows |
|---|---|
| Ring gauges | Current CPU, memory and GPU, each with a 60-second sparkline and a ▲/▼ against ten seconds ago |
| Trend chart | CPU, memory and GPU on one shared timeline. Hovering locks a crosshair and reports all three series at that moment, which is what makes a spike attributable |
| Per-core columns | Instant load of every logical core |
| Core heatmap | The same cores across 40 seconds, newest column at the right — a single pegged core stands out here in a way a bar chart misses |
| Memory composition | A stacked bar of the top six applications by working set, then everything else and free memory, with an icon legend (memory tab) |
| Disk throughput | Read and write history on one shared scale, above the per-volume and per-physical-disk rows (disk tab) |
| GPU engines | Busiest engine first — 3d, copy, video decode, compute (GPU tab) |
| Application tables | Per-application bars, scaled to the largest row shown |
Chart colours (the heat ramp and the composition palette) are deliberately not
theme tokens: a heat ramp needs fixed hues per severity bucket, and a categorical
palette needs evenly separated hues. Both use one fixed lightness and saturation
so they stay legible in light and dark themes. Everything else is --dsw-alias-*.
The page follows the Harness design language rather than inventing one: flat
cards with a .5px hairline border on --dsw-alias-bg-layer-1, --dsw-radius-xl
corners, a 20-21px/600 page title over 14px/600 card titles, tabular-nums
on every figure, and focus rings built from --dsw-focus-ring-*. Depth comes
from layered backgrounds (layer-1 card, layer-2 detail surface, layer-3 wells and
tracks) instead of drop shadows, which is how the shipped pages do it.
Each gauge card carries a faint radial tint and a resource glyph in its own state
colour, so the three cards are distinguishable before any text is read. The live
indicator pulses only while readings are arriving, and every transition is
disabled under prefers-reduced-motion.
The stylesheet uses 27 --dsw-* tokens, all of them ones the shipped Harness
pages already reference — no token name is guessed, so a rename degrades
appearance rather than breaking rendering.
Clicking a gauge — or its title button — opens 占用明细 / Usage details for that resource, which answers "which application is using it?":
| Tab | Rows |
|---|---|
| CPU | Applications ranked by CPU share of the whole machine, with memory beside |
| 内存 | Memory composition bar, then applications ranked by working set with CPU beside |
| GPU | Engine-type breakdown, then applications ranked by GPU utilisation |
| 磁盘 | Throughput history, volumes with used/total/free, then per-physical-disk rates |
The three application tabs also carry a 按占用 / 按增长 switch: usage ranks by the tab's resource, growth ranks by memory movement over the last few minutes. Selecting a row expands it into that application's own processes.
Rows are grouped by application, so twelve msedge processes appear once with a
process count and one icon. Bars scale to the largest row shown; the exact value
is always in the number column.
The detail tables answer "who is using it". Two things go further, because absolute numbers hide the two cases people actually care about.
Growth ranking. The Host captures a per-application memory baseline every 30 s and keeps ten minutes of them. The 按增长 / By growth switch ranks applications by memory movement over the longest window available, so a process leaking slowly rises to the top instead of hiding underneath whatever happens to be largest right now. Applications that did not exist in the baseline are tagged as new rather than reported as growth, movement under 8 MB is treated as noise, and the row states the window it actually measured. In a 62-second observed window the largest movers were a chat client at +42.7 MB, a shell at +41.2 MB, and the Harness itself at −191.0 MB — the last one being memory it had just released, which no absolute reading would ever show.
Single application detail. Selecting any row expands it to that application's own processes — PID, CPU, memory, GPU and start time — plus the executable path it was resolved from. This is where "which of Chrome's nine processes is the problem" gets answered, and it is why the tables group by application in the first place.
Start times come from Process.StartTime, which throws for protected processes;
about 141 of 260 processes on the development machine expose one, and the rest
show a dash.
Time range. The Host keeps thirty minutes of aligned samples and the header offers 1 / 5 / 30 分钟. Whatever the window, the payload is reduced to 180 points, so a thirty-minute view costs the same ~9 KB as a one-minute one, and an out-of-range request is clamped to the nearest offered window rather than trusted.
Session summary. Extremes since the peaks were first accumulated — CPU, memory, GPU and disk latency — each with the moment it happened and which application was to blame at that time, plus the running averages. A live reading answers "what now"; this answers "how bad has it been".
Export. GET …/export?format=csv|json|ndjson&scope=series|apps|full&window=…
answers three different questions, each with the formats that suit it:
| Scope | What it is | Formats |
|---|---|---|
series | One row per sample: CPU, memory, GPU, disk read/write, latency, queue | CSV · JSON · NDJSON |
apps | Per application now: processes, CPU, memory, GPU, growth, executable path | CSV · NDJSON |
full | One bundle with every reading, capability, the config, alert history and session peaks | JSON |
All carry content-disposition: attachment, so the Status tab links to them
directly. Series sampled at different rates leave gaps in each other's rows
rather than being resampled into numbers that were never measured.
apps is point-in-time, not a window average, because per-application history is
not retained — only the current aggregate and the memory-growth baseline. The
growth columns are the exception: they describe a real span and carry the span
they measured.
format=ndjson writes one complete JSON object per line. It is the format a
sampler would log to, and it beats CSV on the three properties that matter for
a log that grows while you watch it:
| CSV | NDJSON | |
|---|---|---|
| Append | Safe, but the header defines the schema | Safe, and each line is self-describing |
| Adding a field | Invalidates every existing file — old rows now have the wrong shape | Old rows stay valid; they simply lack the field |
| Torn write | A partial flush corrupts the row, and often the parse of everything after it | Costs exactly one line |
| Nesting | Impossible — the app's executable path is fine, a nested object is not | Native |
| Reading it | Every spreadsheet | jq, pandas.read_json(lines=True), DuckDB read_ndjson_auto() |
The last row is the practical argument. DuckDB turns a month of NDJSON into SQL or Parquet in one statement, with no import step:
-- 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 remains the default for the same reason it always was: it is what people open in Excel. JSON remains for the case where one document is wanted rather than rows.
Long-term history. One NDJSON file per day under ~/.dsh-system-monitor/history/,
appended in thirty-second batches — writing straight through at one sample a
second would be 86,400 file appends a day for a monitor nobody is watching.
Retention is bounded on purpose. historyDays defaults to 7 and is clamped
to a year, and the prune runs on every flush rather than on a schedule that might
not survive a restart. A monitor that silently fills a disk is a worse bug than
one with no history at all, so this is not configurable into a problem:
historyDays | Effect |
|---|---|
7 (default) | One week of per-second samples, about 60 MB |
0 or negative | History off entirely; nothing is buffered, let alone written |
99999 | Clamped to 365 |
At roughly 100 bytes a line, a day costs about 8 MB. The files are the same NDJSON the export writes, so a month of them is one DuckDB query.
Persistence. Extremes, the alert log and a downsampled chart tail are written
to ~/.dsh-system-monitor/state.json — at most once a minute, and only when a
peak or an alert actually changed. On the next start they are restored, so the
plugin can still answer "has it been like this all week" after a restart:
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
Set persist: false to disable it. Every read and write is wrapped: a read-only
home directory is a valid environment, not an error, and a failed save is
silently skipped rather than surfaced as a plugin fault.
Two things actually run out, so two things are projected: free disk space and commit charge. Both are sampled once a minute into an hour-long series, and both are fitted with a least-squares slope.
The design rule is never state a projection the data does not support:
按最近 12 分钟的速度,容量基本不变 instead of showing nothing.| Case | Result |
|---|---|
| 50 GiB free, losing exactly 1 GiB/day, 10 min window | daysLeft: 50 |
| Same volume, flat | slope 0, daysLeft: null |
| Same volume, 4 min window | slope measured, daysLeft: null |
| Volume gaining space | daysLeft: null — never projects a fill-up |
| Commit +1 GiB over 25 min, 9 GiB headroom | secondsLeft: 13500 (exact) |
| Commit shrinking | secondsLeft: null |
Battery comes from Get-CimInstance Win32_Battery, polled every twelfth
sample because it is a slow query and battery state changes on a scale of
minutes. It is probed inside the loop rather than at startup so a machine where
CIM is unavailable cannot delay the first reading. Note that the
Battery Status performance counter set does not exist on Windows at all — CIM
is the only route — and EstimatedRunTime is reported as zero while charging, so
implausible values are dropped rather than shown as "0 minutes left".
The panel is built for a wall display as much as for a laptop.
Density. 舒适 / Compact switches every size that matters — ring gauge
104→84 px, trend chart 168→108 px, per-core bars 60→36 px, plus tighter padding
and card gaps. Compact fits the whole dashboard on a 1080p screen; the page still
scrolls if it has to, so nothing can become unreachable. The choice is kept in
localStorage and survives a reload.
Responsive columns. The page is capped at 1560 px, the three gauges reflow at
240–276 px, and the core grid and disk card sit in one auto-fit region: stacked
on a narrow window, side by side on a wide one. Previously they were always
stacked, which wasted most of a wide screen.
Trend chart. It is the centrepiece, so it carries more:
−30s-style offsets for short
ones, whichever the reader is actually asking for;Every one of these is measured in the render suite rather than assumed: the harness counts axis ticks, shaded spans and peak dots, and asserts the ring, chart and core-bar sizes for both densities.
Four checks run on every sample and answer "is something actually wrong", not just "what are the numbers".
Resource pressure. Each resource is scored 0–1 against what counts as saturated for it — CPU 90 %, memory 95 %, GPU 95 %, disk 50 ms latency or a queue of 4 — and the worst one is named as the bottleneck in the header line. Capacity is deliberately excluded: a 90 % full disk is not a slow disk. The Status tab draws all four as bars, with the bottleneck highlighted.
CPU throttling. Live clock ÷ nominal, but only while the CPU is above 45 %. A low clock at idle is power management, not throttling, and flagging it would make the chip meaningless. Above the load floor, a ratio under 0.85 raises a chip on the CPU card and the session peak clock stays visible for comparison — on the development machine that is 2.0 GHz nominal against a 4.07 GHz peak.
Disk contention. Avg. Disk sec/Transfer and Current Disk Queue Length
sit alongside throughput on the disk card, in the disk tab and in a latency
history. Throughput alone cannot distinguish a busy disk from an idle one moving
the same bytes; latency and queue depth can.
Alert history. Every alert-level transition is recorded with its reasons, so "when did this start" has an answer. Transitions appear as vertical markers on the trend chart, aligned with whatever the chart was doing at that moment, and as a list on the Status tab. Only changes are recorded, and the log is bounded, so a flapping resource cannot grow it without limit.
Everything runs in the Host half, which keeps one sampling cadence regardless of how many pages are open.
os.cpus() time deltas per logical core, aggregated and per-core.os.totalmem() / os.freemem().fs.statfs over the mounted drive letters, re-scanned every 20 s.