# @xmwengxing/dsh-client-ui-sidebar-perfmon 为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 右侧栏提供实时主机性能监控: CPU / 内存 / 交换内存仪表盘、显卡频率与显存条、CPU / 显卡 / 主板 / 硬盘 温度, 以及可排序的进程列表——支持 Linux / macOS / Windows。 [English](README.md) | 中文 性能监控面板:资源占用与显卡条、四块温度磁贴、进程列表 ## 功能 向右侧栏贡献一个页面,向会话顶栏贡献一个按钮: - **资源占用**:CPU、内存、交换内存三个环形仪表,各带一行辅助信息(核心数与负载 / 已用与总量 / 交换内存状态);仪表下方一条**显卡横条**,左半频率、右半显存, 各自带表示占比的填充;按需统计当前查看会话的项目目录大小;标题栏显示主机名与运行时长。 - **温度**:CPU、显卡、主板、硬盘四块磁贴,按「正常 / 偏热 / 过热」(70 °C 与 85 °C) 着色。头条取该部件**最高**的传感器,多核封装或双硬盘既不会撑宽卡片也读得清, 悬浮列出它覆盖的每一个传感器。各平台能读到什么见[温度](#温度)。 - **Windows CPU 温度可一键补齐**:温度卡片标题条上有 LibreHardwareMonitor 的 胶囊按钮——识别不到 → **安装LHM**(winget 固定包),已装未运行 → **启动LHM** (一次提权启动),运行中 → 按钮消失、温度照常流入。悬浮提示点击前就说明 UAC 弹窗出现在**主机**桌面上;宿主没有 winget 时改为提供官方下载链接。 - **进程列表**:每行一个进程——CPU 占用、常驻内存、占物理内存的比例、PID 与线程数。 支持按 CPU、内存或进程名排序,按进程名或 PID 筛选,拖动列分隔线调整宽度 (宽度按浏览器记忆)。两个数值列永不压缩,侧栏变窄时先牺牲进程名而不是数值。 - **刷新**:同一间隔驱动所有窗口(默认 Linux 2 秒 / macOS 3 秒 / Windows 4 秒), 标签页隐藏时暂停轮询。温度卡片例外,按自己更保守的节奏刷新——温度是绝对读数, 且在 Windows 上读取昂贵。 - **保持唤醒**:远程使用时不再被宿主的自动睡眠掐断连接。浏览器标签页无法阻止系统 休眠(空闲计时只被本地输入或操作系统唤醒锁重置),所以点击由**宿主**执行:新的 `/api/perfmon.awake` 路由启动一个极小的持锁进程——Windows 用 `SetThreadExecutionState`(`ES_CONTINUOUS | ES_SYSTEM_REQUIRED`,宿主存活期间每 5 分钟重新断言)、macOS 用 `caffeinate -i -w <宿主 pid>`、Linux 用 `systemd-inhibit`(只锁 sleep/idle,**绝不锁关机**);持锁进程绑定宿主进程号, GUI 退出或插件卸载即自动释放,再点一次按钮则立即释放。未开启时零开销;开启后 只是一个休眠等待的辅助进程,「日志」是内存里的 10 条环形事件,不写任何文件。 平台缺少对应工具时按钮置灰,并在悬浮提示中说明原因。 任何平台都答不出的数字显示 `—` 并在悬浮提示中说明原因——绝不用 `0` 顶替, 因为 0 看起来像一次真实测量。 ## 环境要求 - **Linux / macOS / Windows**——各平台能回答哪些指标见[平台支持](#平台支持)。 - **DeepSeek Harness `0.2.0-rc.1` 或兼容版本**,使用启动 `@deepseek-ai/dsh-web-app` 的 profile(`web` profile 即是)。 无运行时依赖:宿主半边只用 `node:os` 与各平台自带工具,浏览器半边只用 GUI 已提供的 React。 ## 安装 ```sh # 从 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。卸载:`dsh plugin --profile web remove @xmwengxing/dsh-client-ui-sidebar-perfmon`。 打开方式:右侧栏引导页列出 **性能监控** 卡片,点击即以 tab 打开该页面;此后会话顶栏的 **性能监控信息** 按钮可打开或聚焦同一 tab。 ## 配置 默认值刻意保守:每两秒遍历一次 `/proc`,多个面板共享同一次读取结果。 需要调整时在 profile patch 中覆盖: ```yaml # ~/.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 # 完全隐藏温度卡片 # keepAwake: 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//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 按「100% = 一个核心」归一化**——与 `top` 同一约定,因此 4 核机器上的 8 线程进程可能超过 100%。 - **内存「已用」即「不可用」**——页缓存可回收,因此计入可用而非已用。 每次会话的第一次读数会用一次短采样预热,因此面板首帧显示的就是真实百分比而非 0。 另有两种状态如实呈现:**本窗口内新出现的进程**在有第二帧可作差之前显示 `—`; **未启用交换内存的主机**显示「未启用」,而不是一个 0% 的空环。 ### 项目目录行 统计目录是 CPU 与 IO 密集型工作,仪表下方的**项目目录**行因此**手动触发**:按钮统计 当前正在查看会话的工作目录,扫描中可随时停止(已统计的部分结果带「已停止」说明保留), 且扫描有上限——单目录 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 会回绕。 - **Windows 没有系统级的频率来源。** 既没有 `nvidia-smi` 也没有硬件监控软件的机器, 频率显示 `—` 而显存照常读数——绝不拿猜出来的最大频率编一个百分比。 两个读数共处一行——左频率、右显存,各是一个带填充的轨道(频率的填充相对它自己的加速 上限,显存的填充是真正的「已用/总量」);侧栏窄到放不下两半时换行而不是截断。该条 随面板自身的节奏刷新(`gpuIntervalMs`,默认跟随 `refreshIntervalMs`),因为加载模型时 显存才是关键指标。设 `gpu: false` 可隐藏该行。 ### 温度 温度是绝对值而非计数器,因此按自己更保守的节奏读取:一次读数复用 `temperatureIntervalMs`(默认 15 秒),因为 Windows 上一次读取要花一次一秒以上的 PowerShell 调用。各平台的候选源,以及每个源允许填充的部件: | 部件 | 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)。 - **Windows 上 CPU 温度需要硬件监控软件**:运行 LibreHardwareMonitor **0.9.4** (免安装 zip,以管理员身份运行——0.9.6 移除了本插件所读取的 WMI 提供程序)或 OpenHardwareMonitor;没有时 CPU 磁贴显示 `—` 并说明原因。Intel 的 `Distance to TjMax` 是热余量而非温度,已排除。 - **macOS 需要 root 或自行安装的数据源**:以 root 运行 `powermetrics`,或安装 `osx-cpu-temp` / `istats`。三者都没有时四个磁贴全是 `—`。 ## 隐私 插件读取本机内核计数器,只返回给发起请求的浏览器。它自身不发起任何网络请求, 不外发数据,不写文件。路由受与 GUI 其余部分相同的鉴权保护。 ## 已知限制 - **没有进程属主、命令行与进程树**。每行包含进程名、PID、状态、线程数、CPU 与 RSS。 - **温度取决于平台暴露了什么**(见[温度](#温度));读不到传感器的机器显示 `—` 并说明原因,而不是借用别的部件的数字。 - **磁贴里没有风扇转速、电压或逐核心温度明细**。每个部件的头条是它最高的那个传感器, 所有传感器都列在悬浮提示里。 - **没有历史数据**。面板只显示当前读数,没有迷你趋势图或留存采样。 - **轮询而非推流**。刷新是按间隔请求,而非服务端推送。 - **顶栏按钮只负责打开,不负责切换**。关闭面板由右侧栏自身的控件完成。 ## 许可证 MIT