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

JVM 诊断器 ax2/dsh-jvm-doctor

DeepSeek Harness
ax219 天前有提交已提供中文文档扫描:未发现敏感能力

粘贴线程转储与 GC 日志,换回结构化诊断

30/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

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

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add ax2/dsh-jvm-doctor
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:待复验
是什么
dsh 原生插件 · dev
装得上吗
曾在 dsh 0.2.0-rc.2 下真实安装成功,但当前 dsh 已更新至 0.2.1-rc.5,本站尚未在新版下复验
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 19 天前
档位由哪些信号合成

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

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🟢 扫描:未发现敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描未发现敏感能力:最多只发网络请求、读取自身元数据或只读文件,未发现执行命令、文件写入或敏感信息读取。

扫描于 2026/11/1(0 天前) · 范围 全包扫描(包内未声明入口) · 来源 GitHub 仓库

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

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-llm@deepseek-ai/dsh-tools@deepseek-ai/dsh-util-values@deepseek-ai/schemastery
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 解析线程转储
  • 解析GC日志
  • 结构化诊断输出

面向 DeepSeek Harness 的 JVM 生产报告诊断器:把 jstack/cmd 线程转储、GC 日志、Maven/Gradle 依赖冲突、堆栈跟踪和 Spring 配置差异解析成结构化诊断。

README

由 DeepSeek 最新模型翻译生成
dsh-jvm-doctor 离线、确定性的 JVM 生产报告诊断器 — 一个 DeepSeek Harness 插件,向模型注册 5 个工具:粘贴线程 dump / GC 日志 / 依赖树 / 异常栈 / Spring 配置文本,换回结构化诊断与诚实的解析覆盖率回执。 MIT License · DSH 0.2.0-rc.2 验证通过 · Node ≥ 20.10(运行)/ ≥ 22.6(测试) 永不接触运行中的进程 · 无 jcmd / jstack / jmap / jstat / attach / JMX · 无网络 · 无文件读取 · 无时钟、无随机 · 每次调用都带字节回执 快速开始 dsh plugin --profile web add dsh-jvm-doctor 装完重启应用。想一次装齐四个插件(含 deterministic-tools / py-doctor / win-native)用 @ax2zicode/dsh-dev-toolbox;别和单独安装混用。 家族总览:zicode.com/software 是四个插件、合装包与桌面软件的统一入口。
展开完整文档(README 剩余 1 节)
dsh-jvm-doctor

离线、确定性的 JVM 生产报告诊断器 — 一个 DeepSeek Harness 插件,向模型注册 5 个工具:粘贴线程 dump / GC 日志 / 依赖树 / 异常栈 / Spring 配置文本,换回结构化诊断与诚实的解析覆盖率回执。

MIT License · DSH 0.2.0-rc.2 验证通过 · Node ≥ 20.10(运行)/ ≥ 22.6(测试)

永不接触运行中的进程 · 无 jcmd / jstack / jmap / jstat / attach / JMX · 无网络 · 无文件读取 · 无时钟、无随机 · 每次调用都带字节回执

快速开始

dsh plugin --profile web add dsh-jvm-doctor

装完重启应用。想一次装齐四个插件(含 deterministic-tools / py-doctor / win-native)用 @ax2zicode/dsh-dev-toolbox;别和单独安装混用。

家族总览:zicode.com/software 是四个插件、合装包与桌面软件的统一入口。

15 秒看效果 —— 把异常栈贴进去,模型调用 java_stack_trace,拿回(实测输出):

root_cause: java.lang.IllegalStateException: inner
primary: java.lang.RuntimeException: boom
frames: com.acme.Main.main(Main.java:7) coverage: 100% (3/3, parser 1)

线程 dump 同理:java_thread_dump 给出 Java 级死锁环(waiting_for / holding 链),而不是让你在 500 个线程里自己找。

为什么不让模型自己写 pwsh? 死锁环、GC 停顿 p50/p90/p99、依赖冲突消解、... N more 折叠还原——写成脚本都是几十行,每次还得重新写对;工具是固定实现 + 47 个测试,并且只出结构化事实、不做因果推断。

相邻插件:dsh-deterministic-tools · dsh-py-doctor · dsh-win-native · dsh-dev-toolbox

为什么需要它

JVM 生产排查的证据几乎全部长这样:一段 jstack 输出、一坨 -Xlog:gc 日志、一棵 dependency:tree、一条 Caused by: 链、一份 application.yml。这些文本运维手里已经有了,但模型拿到之后只能:

1. 自己起进程去采 —— 生产环境通常不允许:容器里没有 jcmd、权限与审计不让 attach、事故期间更不该再给 JVM 加负担;
2. 或者肉眼心算 —— 95 行 jstack 里哪几个线程互相持有锁、Found 1 deadlock 之外还有没有第二个环、omitted for conflict with 到底谁赢了,全靠猜,而且不可复现。

dsh-jvm-doctor 只做一件事:把已到手的文本变成结构化诊断。它刻意不做采集 —— 没有 jcmd、没有 jstack、没有 attach、没有 JMX、没有网络、没有文件系统读取。每个工具都是纯函数:同样的输入永远给同样的输出。

顺带解决一个更隐蔽的问题:「解析失败」和「没有异常」长得一模一样。所有工具都返回 unknown_lines / unknown_samples / coverage / confidence,并且把预算导致的截断逐条报数,所以「没发现问题」和「没看懂输入」永远不会被混为一谈。

设计契约:每个结果都是回执

规范返回值固定为:

{
"result": { "…工具自己的载荷…": true },
"meta": {
"bytesIn": 5396,
"bytesOut": 7716,
"truncated": false,
"notes": []
}
}

模型看到的就是 result(对象给格式化 JSON),只有当发生截断或需要解释时才追加页脚:

[bounded] input=2307B output=5615B
[note] 有 7 帧藏在 ... N more 后面,因为外层 trace 被截断或缺失,无法解析

干净的调用不花额外 token;有问题的调用一定说清楚。

边界行为(五种,绝不混淆)

| 情况 | 行为 |
| 输入超 maxInputBytes | 抛错拒绝:报出实际字节、上限,并明确「没有任何截断或部分处理」 |
| 渲染文本超 maxOutputBytes | 按 code point 边界截断 + truncated: true + note(meta.bytesOut 仍报真实载荷大小) |
| 结构化列表超预算(线程数 / GC cause 数 / 冲突数 / 每条冲突的边数 / 异常数 / 每异常的帧数 / 差异条数) | 截断并逐条报数:threads_omitted、causes_omitted、conflicts_omitted、edges_omitted、exceptions_omitted、frames_omitted、changes_omitted |
| 不认识的语法 | 绝不静默丢弃:计入 unknown_lines,unknown_samples 里给出原文,并按覆盖率下调 confidence |
| 无法给出结论 | 返回诚实标记(confidence、findings、errors[]),而不是编一个诊断 |

安装

npm(推荐):

dsh plugin --profile web add dsh-jvm-doctor

预构建 tarball(与 npm 上是同一份产物,无需构建步骤,也避开 pnpm 的 allowBuilds 授权):

dsh plugin --profile web add https://github.com/ax2/dsh-jvm-doctor/releases/latest/download/dsh-jvm-doctor.tgz

要固定版本就把 latest/download/dsh-jvm-doctor.tgz 换成 download/v<version>/dsh-jvm-doctor-<version>.tgz。

本地开发:不安装,直接挂载构建产物。

pnpm install && pnpm build
dsh --profile web --patch ./cordis.local-overlay.yml --dump-config # 先验证层合成
dsh --profile web --patch ./cordis.local-overlay.yml # 再启动

配置

apply(ctx, config) 接受以下配置(全部可选,含默认值):

| 字段 | 默认 | 作用 |
| maxInputBytes | 2097152 | 单份粘贴文本的硬上限(UTF-8 字节),超过即拒绝 |
| maxOutputBytes | 262144 | 渲染文本的软上限,超过即按 code point 截断并标记 |
| maxThreads | 200 | java_thread_dump 序列化的线程条目上限 |
| maxStatusLines | 40 | java_thread_dump 汇总的状态行上限 |
| maxContention | 20 | java_thread_dump 报告的锁竞争队列上限 |
| maxGcEvents | 500 | java_gc_log 保留的 GC cause 种类上限 |
| maxPauseSamples | 5000 | 计算百分位所用的停顿样本上限 |
| maxConflicts | 100 | java_dependency_conflicts 报告的冲突 artifact 上限 |
| maxEdgesPerConflict | 20 | 每条冲突保留的边上限 |
| maxExceptions | 25 | java_stack_trace 保留的异常链条目上限 |
| maxFramesPerException | 60 | 每个异常保留的帧上限 |
| maxChanges | 500 | spring_config_diff 报告的差异条数上限 |
| maxKeys | 5000 | 每份配置文档展平后的键上限 |
| sampleLimit | 5 | unknown_samples 保留的原文行数上限 |
| toolPrefix | '' | 工具名前缀,例如 jvm → jvm__java_thread_dump |
| only | [] | 只注册指定工具(空 = 全部) |

非法值(0、负数、NaN、Infinity)静默回落到默认值,并向下取整 —— 一个坏配置无法把预算关掉。

工具清单(5 个)

| 工具 | 说明 |
| java_thread_dump | 解析 jstack / jcmd <pid> Thread.print:线程普查、归一化与原文状态直方图、daemon 数、每线程 top 帧与锁、Java 级死锁环(含 waiting_for/holding 链)、Object.wait/park 计数、锁竞争队列 |
| java_gc_log | 解析 JEP 158 统一日志与 JDK 9 之前的旧式 GC 行:事件/停顿计数、总/均/最大停顿、p50/p90/p99 百分位、Young/Full 拆分、堆前后区间、cause 直方图、promotion failure,并在有时间戳时估算分配速率 |
| java_dependency_conflicts | 解析 Maven dependency:tree -Dverbose 与 Gradle dependencies / dependencyInsight:同一 artifact 的多版本、omitted for conflict with X、omitted for duplicate、每条边所在深度、逐 artifact 结论摘要 |
| java_stack_trace | 解析异常链:类型与消息、class.method(file:line) 帧、Caused by: 链、Suppressed: 块、... N more 折叠按外层 trace 还原、最深的业务帧、最多 6 条可能原因提示 |
| spring_config_diff | 把两份 application.yml / .properties 展平成点号键后逐键对比(add / remove / change);敏感值在进入载荷之前就替换为 * |

java_thread_dump — 死锁环与竞争队列

输入(节选;真实示例是完整 jstack -l):

Full thread dump OpenJDK 64-Bit Server VM (21.0.4+7-LTS-103 mixed mode, sharing):

"deadlock-A" #40 ... nid=0x2b00 waiting for monitor entry
java.lang.Thread.State: BLOCKED (on object monitor)
at com.acme.order.Inventory.reserve(Inventory.java:61)
- waiting to lock <0x0000000712d00020> (a com.acme.order.Inventory)
- locked <0x0000000712c00010> (a com.acme.order.OrderDao)
...
Found one Java-level deadlock:
"deadlock-A":
waiting to lock monitor 0x... (object 0x0000000712d00020, a com.acme.order.Inventory),
which is held by "deadlock-B"
"Thead-B": ... which is held by "deadlock-A"
Found 1 deadlock.

输出(节选,… 表示省略):

{
"source": "jstack",
"jvm_version": "OpenJDK 64-Bit Server VM",
"captured_at": "2026-01-02 03:04:05",
"thread_count": 9,
"daemon_threads": 2,
"state_histogram": { "RUNNABLE": 1, "WAITING": 1, "BLOCKED": 5, "TIMED_WAITING": 2 },
"raw_state_histogram": {
"RUNNABLE": 1, "WAITING (parking)": 1, "BLOCKED (on object monitor)": 5,
"TIMED_WAITING (sleeping)": 1, "TIMED_WAITING (on object monitor)": 1
},
"deadlock_marker": true,
"reported_deadlock_count": 1,
"deadlock_cycles": [{
"threads": ["deadlock-A", "deadlock-B"],
"locks": ["0x0000000712d00020", "0x0000000712c00010"],
"chain": [
{ "thread": "deadlock-A", "waiting_for": "0x0000000712d00020", "holding": "0x0000000712c00010" },
{ "thread": "deadlock-B", "waiting_for": "0x0000000712c00010", "holding": "0x0000000712d00020" }
]
}],
"lock_contention_candidates": [{
"method": "com.acme.order.OrderDao.save",
"thread_count": 4,
"threads": ["worker-1", "worker-2", "worker-3", "deadlock-B"],
"states": ["BLOCKED"]
}],
"total_lines": 95, "parsed_lines": 95, "unknown_lines": 0,
"coverage": 1, "confidence": "high"
}

state_histogram 是归一化枚举(BLOCKED),raw_state_histogram 保留 JVM 原话(BLOCKED (on object monitor))—— 归一化便于统计,原文便于核对,两者都不丢。

明确拒绝:文本超 maxInputBytes 抛错;只认得 dump 语法的行,任何其它行都进 unknown_lines 并降 confidence,不会当成「线程正常」。

java_gc_log — 停顿统计与分配速率

[2026-01-02T03:04:05.678+0800][info][gc] Using G1
[2026-01-02T03:04:05.678+0800][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 128M->45M(512M) 12.345ms
[2026-01-02T03:04:07.100+0800][info][gc] GC(2) Pause Young (Concurrent Start) (Metadata GC Threshold) 480M->120M(512M) 30.500ms
[2026-01-02T03:04:08.100+0800][info][gc] GC(4) Pause Full (G1 Compaction Pause) 500M->90M(512M) 200.000ms

{
"gc_event_count": 6, "pause_count": 5, "non_gc_lines": 1,
"total_pause_ms": 268.095, "mean_pause_ms": 53.619, "max_pause_ms": 200,
"pause_percentiles": { "p50_ms": 20, "p90_ms": 132.2, "p99_ms": 193.22, "max_ms": 200 },
"young_pause_count": 4, "full_pause_count": 1, "concurrent_cycle_count": 1,
"heap_before_max_bytes": 524288000, "heap_after_min_bytes": 41943040, "heap_total_max_bytes": 536870912,
"unique_causes": 4, "causes_omitted": 0,
"cause_histogram": [
{ "reason": "Pause Young (Normal) (G1 Evacuation Pause)", "count": 3, "mean_pause_ms": 12.532, "max_pause_ms": 20 },
{ "reason": "Pause Full (G1 Compaction Pause)", "count": 1, "mean_pause_ms": 200, "max_pause_ms": 200 }
],
"estimated_allocation_rate": { "bytes_per_second": 31182870, "bytes_per_hour": 112258331344, "window_seconds": 2.522, "events_with_timestamp": 6 },
"findings": [
"1 pause(s) triggered by Metadata GC Threshold: class-loader or metaspace pressure is forcing collections",
"1 full-collection pause(s) present; check old-generation occupancy and humongous allocations",
"heap occupancy reached 97.7% before a collection: collections are running close to exhaustion"
],
"coverage": 1, "confidence": "high"
}

堆大小一律换算成字节(128M → 134217728),百分位用最近秩法在保留的样本上计算;样本被 maxPauseSamples 截断时 pause_percentiles_sampled: true 并附 note。同时认得旧式 [GC (Allocation Failure) [PSYoungGen: ...] 1234K->567K(2048K), 0.0123456 secs] 与 [Times: user=... real=... secs],并在 pause_source 里说明停顿秒数取自内联字段、尾随 secs] 还是 [Times: real=]。

java_dependency_conflicts — 谁被裁掉了

[INFO] com.acme:order-service:jar:1.0.0
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.7.18:compile
[INFO] | \- org.springframework:spring-web:jar:5.3.31:compile
[INFO] +- com.acme:widget-core:jar:1.2.3:compile
[INFO] | \- com.google.guava:guava:jar:31.1-jre:compile
[INFO] +- com.acme:widget-extras:jar:2.0.0:compile
[INFO] | \- com.google.guava:guava:jar:32.1.3-jre:compile (omitted for conflict with 31.1-jre)

{
"source": "maven", "edge_count": 9, "artifact_count": 8,
"omitted_edge_count": 2, "duplicate_markers": 1,
"conflicts": [{
"artifact": "com.google.guava:guava",
"kind": "version_conflict",
"resolved_versions": ["31.1-jre"],
"omitted_versions": ["32.1.3-jre"],
"depths": [1], "min_depth": 1, "max_depth": 1,
"edges_omitted": 0,
"summary": "com.google.guava:guava is requested at 31.1-jre; the build resolved 31.1-jre; 1 edge(s) pruned at depth 1..1"
}],
"coverage": 1, "confidence": "high"
}

Maven 的 +-/\- 与 Gradle 的 +---/\--- 各自成树,-> 记入 requested_version:请求版本与被解析版本分开存,因为「构建实际用哪个」和「谁想要哪个」是两个问题。treeDepthOf 让 Maven 与 Gradle 的深度定义一致(顶层直接依赖 = 深度 0)。

java_stack_trace — ... N more 不是黑盒

org.springframework.web.util.NestedServletException: Request processing failed; …
at com.acme.web.CatalogController.list(CatalogController.java:64)
Caused by: java.lang.NullPointerException: Cannot invoke "…Product.getSku()" because "product" is null at com.acme.catalog.CatalogService.toSku(CatalogService.java:112) ... 15 morejson { "exception_count": 2, "root_cause": "java.lang.NullPointerException",
"chain": [
{ "type": "org.springframework.web.util.NestedServletException", "relation": "primary", "frame_count": 3,
"deepest_application_frame": "com.acme.web.CatalogController.list",
"hints": ["NestedServletException surfaced through com.acme.web.CatalogController.list; …"] },
{ "type": "java.lang.NullPointerException", "relation": "caused_by", "frame_count": 16, "folded_frames": 15,
"folded_resolved": 3, "deepest_application_frame": "com.acme.catalog.CatalogService.toSku",
"hints": ["NullPointerException dereferences a null receiver or return value at com.acme.catalog.CatalogService.toSku; …"] }
],
"total_frames": 7, "folded_frames_resolved": 3, "folded_frames_unresolved": 12,
"exceptions_omitted": 0, "frames_omitted": 0,
"confidence": "medium"
}

折叠帧只从最近的外层异常里还原(Java 的语义如此),还原不出来就计入 folded_frames_unresolved 并降 confidence —— 不编造不存在的帧。application_frames、library_prefixes、test_failure 用来区分业务边界与框架管道。

spring_config_diff — 密钥不落地 json { "left_dialect": "yaml", "right_dialect": "yaml", "left_keys": 8, "right_keys": 8, "added": 1, "removed": 1, "changed": 4, "unchanged": 3, "masked_values": 2,
"changes": [
{ "key": "spring.datasource.hikari.connection-timeout", "op": "add", "left": null, "right": "3000", "masked": false },
{ "key": "server.shutdown", "op": "remove", "left": "immediate", "right": null, "masked": false },
{ "key": "logging.level.root", "op": "change", "left": "INFO", "right": "WARN", "masked": false },
{ "key": "server.port", "op": "change", "left": "8080", "right": "9090", "masked": false },
{ "key": "spring.datasource.hikari.maximum-pool-size", "op": "change", "left": "10", "right": "32", "masked": false },
{ "key": "spring.datasource.url", "op": "change", "left": "jdbc:postgresql://db:5432/orders", "right": "jdbc:postgresql://db-primary:5432/orders", "masked": false }
],
"errors": [],
"findings": ["2 secret-looking value(s) were replaced with *** before comparison"]
}

键名命中 password|secret|token|key|credential 时,左右两侧的值在进入载荷之前就替换为 (masked: true 仍然说明这里有一处变化),所以密码既不会被比较、也不会被回显。.properties 的转义(\: \= \\ \ )与行内注释照常解析;解析不了的行进 errors[] 并带上行号,调用本身仍然是成功结果。

诚实性设计:coverage 与 confidence

所有解析器共用同一套记账(src/core/lines.ts):

- total_lines 按 CR/LF 归一化后计数,单个结尾换行不额外造一行;
- 空行算结构、不算语法:把空行计入 unknown_lines 会让覆盖率取决于报告排版多松散;
- coverage = (total - unknown) / total;
- confidence:coverage ≥ 0.98 且没有可疑信号 → high;≥ 0.85 → medium;否则 low;
- 「可疑信号」指文档解析成功但没有任何主体(0 个线程 / 0 条依赖边),这种情况永远拿不到 high;
- 完全空的输入是 low,不是「100% 覆盖率」的假象;
- unknown_samples 只保留前 sampleLimit 行原文,且永远不丢计数。

离线与安全边界

- 不采集:没有 jcmd / jstack / jmap / jstat / jinfo、没有 attach API、没有 JMX、没有 socket、没有子进程;
- 不读盘:报告文本只能从工具入参进来;
- 不联网:没有任何出站请求,包括遥测;
- 不猜密码:spring_config_diff 在比较之前就把敏感值掩码;
- 可复现:无时钟、无随机、无环境变量读取、无 locale 依赖;同一份文本永远给同一份结果(含 bytesIn/bytesOut)。

开发 sh
pnpm install
pnpm typecheck # tsc --noEmit,0 错误
pnpm test # 47 个用例(5 个工具,用例数 ≥ 工具数)
pnpm build # 产出 lib/(ESM + .d.ts,导入已改写为 .js)

测试分三类:

1. 解析器金值测试(test/core.test.ts,27 例):内联真实的 jstack / 两种 GC 日志 / Maven 与 Gradle 依赖树 / 多层异常链 / YAML 与 properties fixture,断言精确的结构化数字(线程数、直方图、死锁环的 waiting_for/holding、百分位、堆字节数、冲突深度),而不是「非空」;
2. 组装路径测试(test/tools.test.ts,20 例):definition.execute(args, exec) → definition.output.render(),断言 result / meta / 页脚,以及每个工具的 schema 形状与「离线 + 拒绝」描述;only / toolPrefix 过滤、resolveLimits 非法值回落、maxOutputBytes 截断路径;
3. 诚实性测试:故意塞入被截断/被人类改过的 fixture,断言覆盖率、unknown_samples、confidence 如实地变差(例如 100 行里 4 行不认识 → coverage 0.96、confidence medium),并断言每个预算截断都报出精确的丢弃条数(exceptions_omitted、causes_omitted、edges_omitted…)。

兼容性与风险

- 官方插件 API 自称 pre-stable,无冻结承诺;本插件只依赖最稳定的两个面:ctx.tools.register(defineTool(...)) 与 apply(ctx, config)。
- 验证到什么程度(不夸大):tsc --noEmit 0 错误;47 个用例在真实 @deepseek-ai/dsh-tools 上跑通组装路径;lib/ 产物由 research/plugin-smoke.mjs(stub registry)加载并验证 5 个工具全部注册、schema 齐全、无重名、无 default export、dsh.bundle 指向存在且 id 正确的 patch;Node 22 / 24 的 CI 矩阵;npm 上的版本用 dsh plugin --profile <scratch> add 装进一个临时 profile 后,真实 dsh 启动把本包 5 个工具 schema 发给了模型(会话日志核对)。真实对话也跑通了:同一个临时 profile 接上本机 desktop profile 用的那条 LLM 路由后,模型在真实回合里调用了 java_stack_trace(3 行异常链),正确报出 root_cause、帧位置与 100% 解析覆盖率(会话 session-b5681b93)。尚未做:跨多个真实会话的调用统计(这属于 M3)。
- 语法是启发式的:不同 JDK 版本的 dump/GC 文案、各构建工具的输出变体都可能落在已知语法之外 —— 这正是 unknown_lines 存在的理由;覆盖率下降就是「这份报告我没完全看懂」的信号,请以它为准。
- 不做因果推断:工具给的是结构化事实与明确的 finding(例如「97.7% 堆占用触发回收」),不是「你的服务就是这样挂的」。
- GC 分配速率是线性估算(首尾时间戳窗口内的正堆增量),不含分代细节,仅作量级参考。

Roadmap

1. JDK 版本感知:为 JDK 8 / 11 / 17 / 21 的 dump 与 GC 文案分别固定 golden 语料,把版本差异变成测试而不是猜测。
2. 更多报告:jcmd GC.heap_info 文本、JFR 摘要、线程 dump 的差集对比(两次 dump 之间谁新增/谁消失)。
3. 建议引擎:把 findings 从「事实陈述」升级为可核对的检查项(例如「持有锁超过 N 个采样」),并保持每个结论可回溯到具体行号。
4. output.presentCall/presentResult 卡片化,让 Web UI 直接渲染线程表与死锁环图,而不是纯文本 JSON。

License

MIT © ax2

Offline, deterministic JVM production-report diagnostics for DeepSeek Harness — 5 model-visible tools that turn pasted jstack / GC logs / dependency trees / exception chains / Spring configuration into structured diagnostics with an honest parse-coverage receipt.

- Never touches a running process. No jcmd, jstack, jmap, jstat, no attach API, no JMX, no sockets, no subprocesses, no filesystem reads. Every tool is a pure function of the text you paste: no clock, no randomness, no environment reads, so the same input always yields the same output.
- Every call returns a receipt: { result, meta: { bytesIn, bytesOut, truncated, notes } }. Model-facing text is exactly result; a footer appears only when something was clipped or needs explaining.
- 显式失败,绝不静默降级。 超出预算的输入会被拒绝,同时给出两个大小,并保证没有任何内容被部分处理。渲染后的文本按码点边界截断并标记。每个丢弃列表条目的预算都会报告确切数量(threads_omitted、causes_omitted、conflicts_omitted、edges_omitted、exceptions_omitted、frames_omitted、changes_omitted)。
- 无法识别的语法绝不丢弃:计入 unknown_lines,在 unknown_samples 中原样采样,并降低 confidence。完全空白的粘贴内容置信度为 low,而不是伪造的 100% 覆盖率。
- 敏感信息不进入载荷:spring_config_diff 在比较前,会把键名匹配 password|secret|token|key|credential 的值替换为 。

工具:

| 工具 | 它能回答什么 |
| java_thread_dump | 线程普查、归一化与原始状态直方图、守护线程数量、每线程的顶层帧与锁、Java 级死锁环,带 waiting_for/holding 链、Object.wait/park 计数、锁竞争分组 |
| java_gc_log | JEP 158 统一与旧版 GC 语法:事件/暂停计数、总/平均/最大暂停、p50/p90/p99 百分位、Young 与 Full 拆分、堆前后范围(字节)、原因直方图、晋升失败、分配速率估算 |
| java_dependency_conflicts | Maven dependency:tree -Dverbose 与 Gradle dependencies/dependencyInsight:同一构件出现在多个版本、omitted for conflict with X、omitted for duplicate、每条边的深度、每构件摘要 |
| java_stack_trace | 异常链:类型与消息、class.method(file:line) 帧、Caused by: 链、Suppressed: 块、... N more 折叠(对照外层堆栈解析)、最深的应用程序帧、最多六条可能原因提示 |
| spring_config_diff | 把两份 application.yml / .properties 文档展平成点分键,将每处差异归类为新增、删除或修改 —— 敏感值在进入载荷前先做掩码 |

- 安装:dsh plugin --profile web add dsh-jvm-doctor,或用 dsh --profile web --patch ./cordis.local-overlay.yml 在本地挂载构建产物入口。
- 开发:pnpm install && pnpm typecheck && pnpm test && pnpm build —— 47 个测试(解析器黄金值 + 组装后的工具路径 + 诚实性/降级用例),针对真实的 @deepseek-ai/dsh-tools。
- 已在 DSH 0.2.0-rc.2 上验证,运行于 Node 22 和 24;只用到 ctx.tools.register(defineTool(...)) 和 apply(ctx, config),因为官方插件 API 明确处于不稳定阶段。
- 对语法保持诚实:dump 和 GC 的措辞在不同 JDK 版本与构建工具间有差异。覆盖率低于 1 就是信号,说明报告有一部分没被理解 —— 把 unknown_lines 和 confidence 当作答案的一部分,而不是噪声。
预览分享卡片长什么样 ↓ax2/dsh-jvm-doctor
分享 · DeepSeek Harness Hub
ax2/dsh-jvm-doctor

粘贴线程转储与 GC 日志,换回结构化诊断

回到顶部