CPU 飙到 99%服务卡死我第一时间跑上机器执行pstack pid拿到了几千行线程堆栈。盯着终端看了半小时能看出的只有“它在等某把锁”至于哪把锁、谁持有、谁应该释放完全没有头绪。后来我把同一条堆栈文本丢给 Claude几分钟后拿到了一份带证据链的排查报告从锁等待链到可疑调用点甚至给出了下一步该执行哪些命令。这就是我写pstack-claude这个工具的起因不是让 AI 替我做运维而是让 AI 帮我把堆栈里最耗时间的那部分——辨认模式、梳理调用链、圈定嫌疑区域——直接压缩成可执行的判断。这篇内容我会完整拆解这个工具的设计思路、采集与上下文处理的细节、完整的落地步骤以及我在实际使用中踩过的一堆坑。适合后端开发、SRE、性能分析和所有被线程堆栈折磨过的人读完你可以直接把这套方案搬到自己的项目里。1. 项目定位为什么我需要一个“会读堆栈的AI”1.1 pstack 到底给了我们什么pstack是 Linux 上最朴素的堆栈采集工具它通过ptrace挂到目标进程上利用 gdb 读取进程的内存和符号信息把每个线程当前执行到的函数调用链打印出来。对于 C/C 程序pstack pid是最直接的现场取证手段Java 进程通常用jstackPython 进程可以用py-spy dumpGo 程序则直接发一个SIGQUIT信号运行时自己会把所有 goroutine 的堆栈吐出来。堆栈信息的本质是“某个瞬间程序在执行什么”的快照。它非常诚实不会撒谎但也正因如此原始输出极度不便阅读。真实线上的堆栈长这样Thread 0x7f8c1c000700 (LWP 12345): #0 0x00007f8c1b2d6c51 in __lll_lock_wait_private () from /lib/x86_64-linux-gnu/libc.so.6 #1 0x00007f8c1b2d4e3a in _L_lock_101 () from /lib/x86_64-linux-gnu/libc.so.6 #2 0x00007f8c1b2d4c37 in __GI___libc_malloc () from /lib/x86_64-linux-gnu/libc.so.6 #3 0x00007f8c1a3f9822 in operator new(unsigned long) () #4 0x00007f8c1a2a1f45 in std::string::_M_construct() ...信息密度低、地址偏移一长串、重复的模式反复出现。人脑处理这种数据非常吃力尤其是堆栈超过几百行时整个排查过程就变成了在噪音里找信号。1.2 堆栈离“诊断结论”还有几步把原始堆栈直接丢给大模型效果往往也不好原因不是模型不行而是你给的材料本身没有加工。堆栈分析这件事真正的价值在“聚合”和“关联”而不是“逐行翻译”。我总结下来从原始堆栈到可诊断的结论至少要经历四步加工清洗去掉地址偏移、时间戳、编译路径这些对定位问题没有帮助的噪音保留函数名、文件、行号、所在模块。分组相同调用栈的线程合并在一起统计出现次数。十个线程卡在同一个锁上是一回事一个线程卡在某处是另一回事。模式识别在清洗后的堆栈里标记“等待锁”“Native 方法调用”“GC 线程”“网络 IO 阻塞”这类高价值特征。关联上下文把进程状态、线程总数、CPU 占用、采样时间这些信息一起打包让分析者知道当前是什么场景。打个比方给你一千份员工打卡记录让你找出谁在摸鱼你的眼睛看不过来但如果有人先统计出“连续五天十点以后到岗”的名单再让你盯着这几个人看效率完全不一样。pstack-claude做的就是这层“先统计、再分析”的加工然后把加工结果交给 Claude 去判断。1.3 这个工具适合谁应用场景与能力边界pstack-claude适合以下场景线上应用突然 hang 住需要快速判断是不是死锁或者锁竞争没时间人工翻几百行堆栈。Java 进程频繁 Full GC 导致线程停滞jstack输出中堆满了GCLocker相关调用。Python 程序出现全局解释锁GIL竞争需要py-spy dump配合 AI 定位热点函数。定时巡检每天自动采几轮堆栈让 Claude 对比前后差异提前发现隐患。但它不是万能的。堆栈只能告诉你“此刻程序在哪个函数里”没法告诉你“那台机器的网络是不是已经断了半小时”。所以这个工具的输出定位是“排查入口”而不是“最终结论”。它擅长把一块难啃的骨头变成可执行的下一步但真正的根因确认仍然需要结合日志、监控和分布式追踪。2. 整体设计与工具链选型2.1 采集层多语言进程的堆栈采集方案怎么选设计这个工具时第一个问题就是“堆栈从哪里来”。不同语言、不同运行时的采法完全不同我在工具里做了一个简单的自动检测根据进程命令行特征判断该用哪个采集器。工具适用场景典型命令注意事项pstackC/C、任意带符号的本地进程pstack pid依赖 gdb符号不全时只能看到地址jstackJava 进程HotSpot JVMjstack pidJDK 18 以上需要处理新 attach 机制py-spyPython 进程py-spy dump --pid pid不需要重启程序但需要 ptrace 权限gdb attach通用兜底gdb -p pid -batch -ex thread apply all bt最重适合其他方式都失效时SIGQUITGo 程序kill -SIGQUIT pidGo 运行时自带堆栈 dump 能力为什么不用单一采集器因为实际环境太杂了。我曾经遇到过一个混合部署的节点同一个容器里既有 Java 服务又有 Python sidecar如果只做 jstack另一个进程的问题就完全盲区了。所以工具的设计原则是“能采到就行优先级分好”具体顺序可以在命令行参数里覆盖。2.2 上下文工程给 Claude 看的堆栈要过几道筛子很多人在类似工具上翻车不是 API 调用有问题而是 Prompt 上下文构造得太粗糙。把pstack pid的原始输出原封不动发给模型模型会在几千行地址偏移里被噪音干扰产出自然不理想。我在这部分做了一个轻量级上下文工程流程是规范化用正则把0x00007f8c...这类地址偏移替换成#索引把重复的from /lib/x86_64-linux-gnu/...简化成模块名。去重与聚合按“完整调用栈”做 hash相同栈只保留一条并在头部标注出现次数。比如[x12]表示同一个栈出现在 12 个线程里。特征标注对等待锁__lll_lock_wait、pthread_cond_wait、Object.wait、IO、GC、网络请求相关帧打上高亮标记。裁剪和截断单条栈超过 30 帧时截掉重复的通用底层帧保留用户代码入口和当前正在执行的帧。附上元数据进程 PID、进程名、线程总数、采样时间、运行时长、系统负载。这些信息 Claude 不会自己猜必须显式提供。处理后我传给模型的内容大概长这样进程: order-service (PID 25891) 线程总数: 52 | 采样时间: 2025-06-01 14:30:22 可疑聚合栈 Top 3: 1. [x15] 线程卡在 java.sql.DriverManager.getConnection - 调用链: HttpHandler - OrderService.create - DBClient.getConnection 2. [x8] 线程处于 TIMED_WAIT 状态 - 调用链: ThreadPoolExecutor.getTask - ... 3. [x2] GC 线程活跃G1 并发标记阶段这种结构对 Claude 来说直接省去了“大海捞针”的过程它可以把精力集中在真正的分析上。上下文工程是整个工具跑得顺不顺的关键比模型选哪个还重要。2.3 分析接入Claude API 与 Claude Code 两条路径pstack-claude的“分析引擎”有两种接入方式我两个都做了因为使用场景完全不同。第一种是 Claude API 直连。适合完全自动化的场景比如定时巡检、故障自愈流程里自动触发分析。工具内部用anthropic的 Python SDK 拼装消息把加工后的堆栈作为用户消息传入system prompt 设定“你是一名有 10 年经验的 SRE 专家”之类的角色约束并把输出格式固定为 Markdown 报告包含摘要、热点现象、可能原因、建议命令四个部分。这种方式不需要人工介入拿到报告直接入库或告警。第二种是 Claude Code 交互式接入。适合人在现场的排查场景。我在终端里启动claude让它读取已经生成好的堆栈分析报告然后自然语言追问“第二个锁等待的持有者是谁”“这个 GC 线程是不是导致 CPU 飙升的元凶”等。Claude Code 的好处是它能在同一个会话里执行命令、读取文件、跑诊断脚本等于把整个排查过程变成了对话式协作。这两条路径在代码上共享同一套 Prompt 模板和堆栈加工逻辑只是调用方式不同。API 路径适合“无人值守”Claude Code 路径适合“人在环上”。2.4 扩展为 MCP Server让 Claude Code 直接操作整个排查流程既然 Claude Code 已经能自己干活了那pstack-claude作为老式脚本就有点浪费。MCPModel Context Protocol协议出现后我在项目里加了一个 MCP Server 包装层把“采集堆栈 预处理 生成报告”变成一个可被 Claude Code 直接调用的工具。这样一来你在 Claude Code 里直接说“帮我看看 PID 25891 这个进程是不是卡在锁上了”Claude 会自己判断需要调用analyze_process工具然后自动执行采集、分析、写报告最后把结论返回给你。添加一个本地 MCP Server 到 Claude Code 里的命令大概是这样claude mcp add --transport stdio --scope user pstack-claude -- node /usr/local/bin/pstack-claude-mcp.js如果你在项目里只想让协作同事也能用可以通过.mcp.json声明{ mcpServers: { pstack-claude: { command: node, args: [/usr/local/bin/pstack-claude-mcp.js] } } }注意MCP Server 和 API 调用并不冲突MCP 让 Claude Code 具备操作能力API 让无人值守脚本具备分析能力。实际使用中我把两者组合起来效果比单用任何一个好很多。3. 实操全流程从一条命令到一份诊断报告3.1 安装与环境准备先说环境要求。pstack-claude是 Python 脚本分析端依赖 Claude API 或 Claude Code。全套流程准备如下Python 3.10建议用虚拟环境隔离依赖。pip install anthropic psutil。anthropic是官方 SDKpsutil用来读取进程元数据。Claude Code 不是硬性要求但如果要交互式排查建议装npm install -g anthropic-ai/claude-code要求 Node.js 18安装后执行claude --version验证。配置 API Key不要硬编码进代码里直接 export 到环境变量。export ANTHROPIC_API_KEYsk-ant-xxxxxx提示API Key 是敏感信息生产环境建议放进密钥管理服务而不是写在 shell 历史里。由于我经常在容器里排查容器内要保留pstack或gdb否则采集层会直接失败。Debian/Ubuntu 系可以用apt install gdb装齐。3.2 快速开始采集一个进程的堆栈并生成报告核心代码不长我贴一个精简版保留最关键的流程import os import sys import subprocess import psutil from anthropic import Anthropic STACK_COMMANDS { java: jstack {pid}, python: py-spy dump --pid {pid}, c: pstack {pid}, } def collect_stack(pid: int, runtime: str) - str: cmd STACK_COMMANDS.get(runtime, pstack {pid}).format(pidpid) return subprocess.check_output(cmd, shellTrue, stderrsubprocess.STDOUT, textTrue) def simplify(stack: str) - str: lines [] for line in stack.splitlines(): line line.strip() # 去掉地址偏移 line line.replace(0x00007f, #) if line and from /lib/ not in line: lines.append(line) return \n.join(lines) def analyze(pid: int, runtime: str, content: str) - str: client Anthropic(api_keyos.environ[ANTHROPIC_API_KEY]) proc psutil.Process(pid) meta f进程: {proc.name()} (PID {pid}) | 线程数: {proc.num_threads()} prompt f{meta} ---- 堆栈信息 ---- {content} 请分析当前进程是否存在异常死锁、锁竞争、线程卡死、GC问题或其他可疑点。 输出 Markdown 格式报告包含1. 摘要 2. 热点问题 3. 证据链 4. 建议命令。 resp client.messages.create( modelclaude-sonnet-4-20250514, max_tokens2000, system你是一名有 10 年经验的后端性能分析专家擅长线程堆栈定位问题。回答要具体、克制、不臆测。, messages[{role: user, content: prompt}], ) return resp.content[0].text使用方式python pstack_claude.py --pid 25891 --runtime java --output report.md脚本内部流程是探测进程类型也可以通过--runtime强制指定- 执行采集命令 - 清洗和聚合 - 构造 Prompt - 调用 Claude API - 把返回的 Markdown 报告写入指定文件。执行后生成的报告大概长这样## 摘要 进程 order-service 存在明显的数据库连接获取阻塞15 个线程卡在 getConnection。 ## 热点问题 - 线程池连接耗尽多个线程在等可用连接疑似连接池太小或连接泄漏。 ## 证据链 - 15 个线程的栈同时包含 DBClient.getConnection且调用栈上层都是业务接口入口。 ## 建议命令 1. 确认连接池配置jstat -gcutil pid 2. 抓取两轮 jstack 间隔 10 秒对比线程是否还在等待 3. 检查数据库端活跃连接数这份报告的价值在于它把“结论”“证据”“下一步动作”分开你拿到后不是被一个问题牵着走而是有了一条清晰的排查路径。3.3 在 Claude Code / VS Code 里配合使用有经验的工程师排查问题时不会满足于一次性报告总要追问几轮。pstack-claude和 Claude Code 结合后的体验比纯 API 顺畅很多。终端里启动 Claude Codeclaude然后在交互界面里说请读取 report.md 和 stack_raw.txt重点分析第 2 个热点。 接着检查当前进程的网络连接状态看是否有大量 TIME_WAIT。Claude Code 会自己安排工具调用顺序比如先cat report.md再ss -antp | grep 25891最后给你一个综合判断。整个过程像带了一个实习生在帮你跑腿你只需要把关方向。VS Code 用户可以在 IDE 内置终端里跑安装 Claude Code 的 VS Code 扩展。在侧边栏或终端面板打开 Claude Code 会话。在会话里直接执行python pstack_claude.py --pid 25891 --runtime java然后继续追问结果。实际上 VS Code 的本质是给你一个顺手的终端真正干活的是 Claude Code 的 Agent 能力。它可以读文件、执行命令、解析输出所以排查完堆栈之后还能顺手帮你查日志、看监控曲线截图整个闭环非常顺。3.4 看懂结果结论与证据要分开看pstack-claude输出的报告我很在意一点就是“哪些是确定的哪些是推测的”。AI 在生成文本时天然倾向自信表达如果它告诉你“这就是连接池泄漏”你把它当实锤去改代码可能浪费时间。所以在 Prompt 模板里我做了约束报告必须区分“证据链”从堆栈里直接看到的事实和“推测原因”基于经验的判断。实际操作时我会再追问一句“以上判断中哪些是堆栈直接证明的置信度打分。”这样能明显降低误判率。另外尽量不要只采样一次就下结论。线程调度具有随机性一次采样可能恰好错过了真正卡住的线程。我一般建议连续采样 3 次、间隔 10 到 30 秒对比不同时间点的堆栈差异稳定性会好很多。4. 常见坑与排查技巧实录4.1 采集端attach 权限与容器隔离堆栈采集的第一步就经常遇到权限问题。Linux 内核的ptrace_scope安全机制限制了普通用户 attach 到非子进程。报错通常是Operation not permitted检查方式cat /proc/sys/kernel/yama/ptrace_scope如果输出1只有父进程或具备 CAP_SYS_PTRACE 的进程才能 attach。临时放开可以执行echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope但这只是临时方案生产环境不建议直接改内核参数更稳妥的办法是用 sudo 运行采集命令或者把采集进程以 root 权限跑。容器环境下还有一个坑默认容器看不到宿主机全局的进程表需要给采集容器挂--pidhost跑 Java 进程的容器还要注意 jstack 必须能 attach 到目标 JVM有时候需要--cap-addSYS_PTRACE才能成功。4.2 Claude Code 安装与自动升级时的 npm 权限问题Claude Code 的安装一直是大多数人翻车的高发地尤其是 npm 全局目录权限不对时会看到auto-update failed: no write permission to npm prefix这个问题的根源是 npm 的全局安装目录属于 root当前用户没有写权限。最干净的解决方式是把 npm 全局目录改到自己用户目录下npm config set prefix ~/.npm-global export PATH$PATH:~/.npm-global/bin设置完重新安装npm install -g anthropic-ai/claude-code然后验证claude --version不要在有写权限问题的路径下直接sudo npm install -g虽然能装上但后续自动升级还是会反复报权限错误而且会造成文件归属混乱。另外如果你不想全局安装临时跑一次也可以用npx anthropic-ai/claude-code不过每次启动速度会略慢体验不如全局安装。4.3 Windows 上桌面版与虚拟机平台依赖问题如果你在 Windows 上安装或启动 Claude 桌面版有时会碰到类似下面这种提示Claudes workspace requires the virtual machine platform on Windows. Enable it...这个问题的本质是新版桌面端使用了虚拟化工作区隔离机制Windows 必须开启虚拟化功能才能运行。两步处理在 BIOS 中确保 CPU 虚拟化已开启Intel VT-x 或 AMD-V。以管理员身份打开 PowerShell执行Enable-WindowsOptionalFeature -Online -FeatureName HypervisorPlatform执行完重启操作系统。如果你不想动系统组件还有一个替代方案放弃桌面版改用 Claude Code 命令行工具在终端里完成对话和任务执行它不依赖 Windows 虚拟机平台。还有不少人选择在 WSL2 里跑 Claude Code这也是可行的WSL2 本身依赖虚拟化平台如果系统没开先执行上面的命令把基础打牢。顺带一提这类“Windows 功能缺失”的问题和网络、地区无关纯粹是本机组件开关的问题检查顺序建议是BIOS 虚拟化 - Windows 可选功能 - 重启按这个顺序排查最快。4.4 分析端采样时机、符号缺失与 AI 误判堆栈采集得再准确分析端照样有坑。最常见的三个问题第一采样时机不对。进程可能只是偶发卡顿你采样的那一刻恰好恢复了堆栈看起来一切正常。解决办法是连续采样两到三次间隔 10 秒以上并把“多次采样结果一致”作为可靠判断的前提。第二符号文件缺失。堆栈里如果出现大片的??或者[0x7f8c...]说明程序没有携带符号表或符号文件路径不对。对 C/C 程序确保编译时带-g线上环境可以保留单独的 debuginfo 包供 gdb 使用。对 Java 程序注意 jstack 输出的 native 栈部分本来就可能只有地址这是正常的重点看 Java 栈帧即可。第三AI 过度推断。模型看到堆栈里出现getConnection就顺着猜测连接池问题但它并不知道你的数据库连接池监控显示正常。我的处理方式是在 Prompt 里固定一句“如果堆栈本身不能直接证明某个结论标注为推测不要使用肯定语气。”同时在追问时告诉它补充监控数据哪怕只是贴一段日志判断准确率都会有质的提升。5. 进阶玩法把 pstack-claude 变成巡检机器人的“手和眼”5.1 定时采集与基线对比工具做出来后我第一个想到的用法不是故障时临时用而是让它每天定时干活。通过 cron 或 systemd timer 每 5 分钟采集一次堆栈并和上一次的聚合结果做对比哪个调用栈的线程数量在上涨哪个等待模式是新增的哪个之前出现过但这次消失了。基线很关键。第一次跑的时候找一个服务稳定的时段采集一份堆栈作为基线存档。之后每一次采样都和基线做 diffAI 只需要关注“新增的、变化的”那一部分而不是把整个堆栈重新分析一遍。这个思路和监控系统里的动态阈值很像以常态为参照异常才会真的跳出来。5.2 告警触发与报告归档在定时任务里加一层条件判断如果聚合结果中有超过 20 个线程卡在同一栈帧或者某个等待模式的占比超过历史统计的 3 倍标准差就触发告警。告警不是简单喊一嗓子而是自动跑一遍完整分析流程生成 Markdown 报告按时间戳命名归档report-20250601-143022.md原始堆栈和分析报告放在同一个目录方便事后回溯。这个做法在复盘故障时特别好用因为你不用凭记忆还原现场报告里的证据链就是当时最真实的记录。5.3 更完整的现场让数据库、日志和监控一起进上下文堆栈分析的局限在于它只看得到“程序在干什么”看不到“外部系统发生了什么”。所以我通过 MCP 生态把更多数据源接了进来Claude Code 可以在一次会话里读取 MySQL 慢查询日志、拉取 Redis 延迟监控、查看网关错误率。假设堆栈显示多个线程卡在JDBC executeQuery传统做法是你还得手动去数据库查慢 SQL。但接入了 MCP 工具后Claude Code 会自动把慢 SQL 日志 grep 出来和你手里的堆栈做关联直接告诉你“就是这条 SQL 引起的连接长时间占用”。这一步把“猜测然后验证”变成了“现场综合分析”效率完全不一样。配合常规的日志分析工具、APM 系统pstack-claude的角色从“堆栈分析器”进化成“故障现场助理”它不替代任何现有监控而是把所有信号在 AI 会话里拼接成一个完整画面。我在实际使用中最大的体会是这工具的价值不在于替你做决策而在于帮你把发现问题的速度从小时级压缩到分钟级。刚开始我还会逐帧去核对它的分析结果后来跑的次数多了发现它列出的证据链确实都对应得上才敢让它在巡检任务里全自动出报告。如果你也想自己搭一个建议从最朴素的想法开始先写一个脚本把堆栈采集下来再用手头的 Claude API 试试直接分析原始输出感受到噪音的影响后你就会理解为什么预处理那一步如此重要。最后分享一个我踩过几次坑后养成的习惯第一次跑一定要先采一份正常状态的堆栈当基线往后所有排查都拿它做底子AI 的判断准确率会高出一大截。
阅读完成 · 觉得有帮助?