dsh-memory-watchdog
DSH Desktop 的 Windows 提交内存看门狗:每 5 秒采样提交内存,把它归因到 DSH 进程树与占用最高的进程,在 DSH 不是主要占用者时阻止自动重启,并给出只读的分页文件建议、7 天崩溃时间线与事件导出;自动重启与崩溃后自动续接默认都是关闭的。
安装
dsh plugin --profile web add dsh-memory-watchdog
沙箱测试结果
| 阶段 | 结果 |
|---|---|
| 1 安装 | ✓ |
| 2 列装 | ✓ |
| 3 挂载 | ✓ |
| 4 启动 | — |
四阶段在隔离沙箱里跑:安装(有网)→ 列装 → 挂载 → 启动(后三步断网)。
README
DSH 内存守护(dsh-memory-watchdog)
Windows 版 DSH Desktop 的提交内存(commit charge)看门狗。它持续采样系统提交内存,给出谁在吃内存的归因、分页文件只读建议与最近 7 天的崩溃时间线,并在进入预警或危险区间时给出页面内提示;自动重启默认关闭,崩溃后自动续接默认关闭。它还能把 Host 进程内的 V8 堆分配按插件归因,给出每个插件的内存占用排名与泄漏风险分级。
- 目标平台:Windows x64
- DSH 版本:
>=0.2.0-rc.1 <0.3 - 自动重启:默认关闭(opt-in)
- 崩溃后自动续接:默认关闭(opt-in)
- 插件内存归因:默认开启(零开销的雷达 + 周期堆分配采样)
- 采集方式:随插件生命周期运行的轻量 PowerShell 采集进程,只读性能计数
- 不依赖 WMI / CIM,不安装服务、计划任务或原生组件
自动重启默认关闭
插件加载后立即开始监控和预警,但不会自动重启任何进程,直到你在 DSH 的
“设置 → 插件 → DSH Memory Guard”中打开开关。设置页会常驻显示开启后的完整代价说明,不需要展开或确认弹窗。
关闭开关会立即取消尚未开始的待执行重启;内存监控与预警继续运行。
阈值可以调(默认仍是文档值)
设置项 · 默认 · 范围 · 说明
warningPct · 80 · 50–95 · 进入 warning 的提交内存百分比
criticalPct · 90 · 55–99 · 进入 critical 的百分比,强制高于 warningPct
criticalAvailMb · 2048 · 256–32768 · 可用 commit 低于该值时也判 critical
confirmSamples · 3 · 1–10 · 状态切换所需的连续样本数
cooldownMinutes · 30 · 5–1440 · 两次重启之间的最小间隔
restartGatePct · 20 · 0–100 · 重启门限;0 表示关闭该判定
pluginMemory · true · 开关 · 插件内存雷达与插件视图
pluginSampling · true · 开关 · 是否按周期自动做堆分配采样
pluginSampleMinutes · 15 · 5–120 · 周期采样的间隔(分钟)
pluginSampleSeconds · 20 · 5–120 · 每次采样的窗口长度(秒)
pluginLeakMb · 32 · 8–512 · 判 high 的每小时增长阈值(MiB),medium 取其 40%
这些字段都是 volatile 设置,改完立即生效,不需要重启 DSH;超出范围的输入会被夹到边界值,criticalPct 永远比 warningPct 高。危险百分比另有绝对兜底:达到 95% 时无论可用量多少都算 critical。
它做什么
- 每 5 秒读取一次 Windows 提交内存已用量、提交上限、物理内存总量与可用量。
- 使用连续样本确认状态(默认 3 个),避免单次尖峰误判;使用迟滞避免在阈值附近反复切换:
状态 · 进入条件(默认连续 3 个样本) · 退出条件
normal · 从 unknown:连续 3 个有效样本且 <75% · —
warning · >=80% 或 可用 commit <=6 GiB · <75% 回 normal
critical · >=90% 且可用 <=2 GiB,或 >=95% · <85% 回 warning
unknown · 数据缺失、无效或样本超过 60 秒 · 连续 3 个有效样本按区间离开:<75% 回 normal,热区间进 warning / critical
- 每 30 秒做一次提交内存归因,列出 DSH 进程树占比与占用最高的进程。
- 每 5 分钟读取一次分页文件与提交上限配置,给出只读建议。
- 每 10 分钟汇总一次最近 7 天的崩溃与内存事件时间线(DSH 崩溃日志 + Windows 系统日志)。
- 持续统计每个插件注册的定时器与监听器(零采样开销);采样开启后每 15 分钟抓一段 20 秒的堆分配窗口,按
node_modules/<包名>排名,并给出每小时增长与泄漏风险分级;面板可手动采样、导出.heapprofile或写一份堆快照。 - 保留最近 50 条事件历史,可在面板查看并一键导出 JSON;另有“复制诊断信息”按钮。
- 在页面内显示当前状态、动作状态、commit 数值、可用 commit、物理内存、Host RSS、冷却剩余时间、最近错误和最近一次重启记录。
- warning / critical / 恢复 / 采集异常 / 重启结果 / 设置变更通过页面内 toast 提示;右下角常驻一个状态胶囊,点击胶囊就地展开摘要浮层(压力百分比与已提交/上限、当前状态与下一步动作、自动重启能力、活动检测、最近事件与错误、DSH 占比、重启门限结论、占用最高的三个进程、插件内存汇总与前五个插件),再点一次、按 Esc 或点浮层外部收起;浮层里的数据与设置页卡片同源,底部另有一个「刷新」按钮可立刻重新取一次状态。
- 采集抖动不打扰:因为样本没送到而进入 unknown 时,提示延迟 2 分钟才弹;这 2 分钟内样本恢复,就只在事件里留两条记录、面板记一个「采样延迟 N 次」计数(摘要浮层与诊断文本里可见,含最大滞后时长),不弹任何 toast。采集器真的停了(非样本延迟导致的 unknown)仍然立刻提示。
- 插件重启后仍会显示冷却状态;冷却记录保存在本机。
提交内存归因与重启门限
- 归因使用一次
CreateToolhelp32Snapshot进程枚举 + 每进程GetProcessMemoryInfo(PrivateUsage),
分母取系统 commit 已用量;只读,不读取其他进程的内存内容,不使用 WMI/CIM。本机一轮约 1.9 秒(含 PowerShell 启动)。
- 面板给出 DSH 进程树的进程数、合计 commit、占系统提交的比例,以及 Top 5 进程(进程名 + PID + commit)。
- 重启门限:默认只有 DSH 进程树占到提交内存的 20% 以上才允许自动重启。占比不足、归因数据缺失或超过 60 秒过期时一律不重启(fail closed),并在面板与 toast 说明原因;每个 critical episode 只会提示一次,不会刷屏。把
restartGatePct设为 0 可以关闭该判定。
分页文件建议(只读)
- 读取注册表
Session Manager\Memory Management的PagingFiles/ExistingPageFiles、分页文件实际大小,以及GetPerformanceInfo的提交上限与物理内存。 - 提示项:没有分页文件、提交上限只比物理内存多不到 2 GiB、分页文件全部落在系统盘(内存 ≥16 GiB 时)、系统自动管理。
- 插件不会修改任何系统设置,也不会代你在 Windows 虚拟内存对话框里点确认;调整分页文件需要你手动操作并重启生效。
崩溃时间线
- 汇总
%APPDATA%\@deepseek-ai\dsh-desktop\logs\crash-*.log(取--- error ---段首行)与系统日志中的
2004(虚拟内存耗尽)、26(低虚拟内存提示)、41(意外关机)事件,默认窗口 7 天、最多 20 条。
- 同时读 Windows 应用日志里的
1000/1001/1002(应用崩溃 / 无响应),从中解析故障模块、异常码与 fault bucket:时间线条目后面会带[模块名],标题行显示「应用故障 N 次」。故障模块只取文件名(例如node.exe),正文里的其余字段按 120 字符截断。 - 同一次事故产生的多份日志(
main+renderer+web-boot)合并成一条,优先级 OOM > 应用故障 > host > main > renderer > 应用无响应 > web-boot。 - 只读本机文件,不上传任何数据。日志正文可能包含本机路径,面板只显示错误首行,导出的诊断文本不含日志正文与会话完整 id。
- 崩溃前尾部与 OOM 归类:上次运行的黑匣子若与某条崩溃记录相差不超过 2 分钟,该条目会挂上最多 12 个点的尾部(
trail),面板在条目下展开「崩溃前黑匣子」,逐点显示提交占用、堆用量与外部内存。同时按最后一个点归类:堆使用率 ≥95% 记oom-heap,external + arrayBuffers≥1 GiB 且占 RSS ≥50% 记oom-external,提交内存 ≥90% 记oom-system;三者都不满足就不下结论。归类是启发式,不是崩溃转储的证明。
崩溃后自动续接(默认关闭)
开启 resumeAfterCrash 后,插件会在 DSH 重启后扫描会话,对被中断的对话注入一条“先核对当前状态,再从中断处继续”的提示:
- 只认
turn/end的reason.kind === 'interrupted';你手动停止的aborted永不续接。 - 只处理 12 小时内有过人类提示词、当前未在运行、非空白的会话;距中断需静置 20 秒,避开刚打开会话时的写入竞争。
- 每个会话每小时最多 2 次尝试;超过 1 小时没有再续就把计数归零。
- 子代理被打断时,父会话即使自己已经
completed也会被唤醒一次,并把该子代理标记为已处理,避免僵尸子代理反复唤醒父会话。 - 注入用的
requestId形如memory-watchdog-resume-<sessionId>-<turn>[-child],同一轮同一来源重复扫描不会二次注入。 - 启动后按 20 秒 / 60 秒 / 180 秒 / 600 秒各扫一轮,被静置期推迟的会话按到期时间自动重排;面板也可以手动“立即扫描”。
- 账本保存在
$DSH_HOME/memory-watchdog/resume.json,面板展示会话短 id、轮次、来源与尝试次数。 - 续接的 turn 使用该会话原有的工具与权限运行——这是开启该开关前最需要权衡的一点。
插件内存(按插件归因与泄漏判断)
所有 host 插件跑在同一个 V8 isolate 里,Node 不按模块记账,所以这里给的不是「每个插件的 RSS」,而是两种能解释的信号:分配归因与驻留趋势。表格的底稿是当前 profile 的插件清单:启动时扫 node_modules 下所有 package.json 带 dsh 字段的包(@scope/ 也算),每个插件先占一行并带上识别到的版本,所以即使还没采样也能看见装了什么;采样窗口到达后再填字节数、占比与增长。装到别的 profile 或全局目录的插件不在清单里。
- 雷达(默认开,零采样开销):包装
setInterval/clearInterval与EventEmitter的on/once/addListener/removeListener/off/removeAllListeners,用注册栈把每个定时器和监听器记到直接调用者所属的包名下。归属按「已安装插件目录」做最长前缀匹配,本插件自己的帧、node:内置以及框架/本地脚本的帧直接跳过而不是算到调用方头上(早期版本按「第一个 app 帧」归属,每写一行日志会被算成几个监听器泄漏,已修)。一次都没归因成功时面板会告警,并显示「已归因 N 次 / 丢弃 M 次」。卸载时完整还原全局函数。 - 周期采样(默认开):每
pluginSampleMinutes分钟用HeapProfiler.startSampling(samplingInterval4096)抓pluginSampleSeconds秒窗口,只保留结束时仍然存活的分配;按采样栈里最近的 JS 帧 URL 归到node_modules/<包名>(含@scope/pkg、app.asar/dsh/<pkg>、.dsh-src/<pkg>),没有 JS 帧的 native 字节单独计一栏,不摊给任何插件。启动约 45 秒后会先跑一次基线采样,之后按周期进行;窗口历史保留 48 个(默认 15 分钟一轮 ⇒ 约 12 小时的趋势)。 - 风险分级:窗口跨度不足 10 分钟时只给 unknown,不做结论;足够长时按最小二乘算每小时字节增长,达到
pluginLeakMb判 high、达到其 40% 判 medium;定时器+监听器总量增量达到 5 也升 medium。high/medium 会列出判定理由(bytes-growth/handle-growth/short-span/not-enough-data),high 时卡片直接建议「只重载该插件」。 - 分配点下钻:风险为 high/medium 的插件会列出前三个热点分配点,形如 `lib/xxx.js:123 · grow ·
…(truncated)
以上内容来自 npm 上的 README,我们只做格式化,不改内容。
版本
npm 上共发布:10,最后更新 2026-11-02