上周线上出了一个怪问题:一个后端服务进程突然CPU飙到100%,请求全部超时,但日志里没有任何报错。我第一反应是上去用pstack抓一下线程堆栈,结果pstack输出出来一大片,全是十六进制地址和函数符号,看得我头皮发麻。正好那段时间我在折腾Claude Code,顺手就做了一个叫pstack-claude的小项目:把pstack采集到的堆栈文件丢给Claude Code去做自动分析。这篇文章就聊聊这个项目的完整思路、安装过程和实际效果,适合也遇到读堆栈想哭、又正好想体验Claude Code的朋友。
1. 为什么要在 Claude Code 里接一套 pstack 分析流程
1.1 pstack 到底是干嘛的
pstack是Linux下非常经典的进程诊断工具,它做的事情一句话概括:attach到一个正在运行的进程上,把进程内所有线程的调用栈dump出来。很多发行版里它本质上是gdb的封装,背后执行的动作差不多是“gdb attach ,然后thread apply all bt”,最后输出每个线程从当前执行位置到入口函数的一整条调用链。
这个工具解决的场景很典型:进程突然hang住、CPU飙高、死锁、或者某个线程卡在奇怪的地方。比如一个服务进程所有请求都超时了,但业务日志又一切正常,这时候pstack能告诉你进程到底在“忙什么”。它是产线事故排查里少数能拿到“第一现场”的手段,比抓日志、看监控更接近问题本质。
不过pstack也有让人头疼的地方:线程一多,输出就是几百行甚至上千行;符号表不完整的时候,看到的全是地址和动态库偏移;再加上线程状态、锁等待、内核态调用纠缠在一起,人工一行行读非常耗时。这也是我决定把它交给Claude Code的原因。
1.2 Claude Code 怎么补上“人工读堆栈”的短板
Claude Code是Anthropic官方推出的终端AI编程助手,它可以在终端里直接执行命令、读取本地文件、然后结合上下文回答你的问题。这意味着它不只是个聊天机器人,而是一个能“看到”你项目环境的真实工具。
人工读pstack堆栈,最痛苦的是不知道从哪看起。几兆的堆栈文件里,到底哪个线程是关键?哪些栈帧是噪音?哪些调用序列指向锁竞争?这些判断需要经验和上下文。Claude Code恰恰擅长这种“给一堆文本,找出重点并归纳结构”的活儿:它会按线程状态、函数调用层级、锁等待关系把堆栈重新组织成一份摘要,再结合你补充的进程信息给出排查建议。
pstack-claude这个项目就是想搭一条自动化的流水线:pstack负责采集现场,Claude Code负责分析和出报告。前者解决“拿不到数据”的问题,后者解决“看不懂数据”的问题。两个工具都不新,但把它俩串成一个可复用的命令行流程,是我个人觉得最实用的部分。
2. 环境准备:安装 Claude Code 并跑通基础命令
2.1 安装前的依赖检查
Claude Code本身是个npm包,所以最基础的要求是Node.js环境。我用的是Ubuntu 22.04台机器,Node版本20.x,npm版本10.x。建议Node.js至少18以上,太老的版本会出现依赖解析问题。
先检查两个版本号:
node -v npm -v如果node命令不存在,推荐用nvm安装和管理Node版本,避免和系统包管理器里的版本冲突。装完之后确认npm registry能正常访问,这个命令也是后续安装Claude Code是否能成功的前提。
Windows用户需要注意,Claude Code的设计一开始就是面向类Unix终端的,在Windows上推荐使用WSL安装,别直接塞进cmd或者PowerShell里。我在WSL里跑下来比较稳,文件路径和pstack这类系统工具也都顺带兼容了。
2.2 安装 Claude Code 的正确姿势
安装本身很简单,全局安装官方包:
npm install -g @anthropic-ai/claude-code装完验证版本:
claude --version接下来运行claude命令会进入首次登录流程,在终端里会生成一个授权链接,用浏览器打开完成账号授权之后,回到终端就能直接对话了。
这里的坑主要是权限问题。如果之前用sudo装过Node或者npm的全局目录被root占用,执行claude时可能会发现写不到npm prefix目录,导致自动更新失败。后面第5章我会单独讲,这里先记着:尽量用普通用户权限做全局安装,别全程sudo。
从终端进入交互界面后,随便问一个问题,比如“你现在能不能读取当前目录下的文件”,确认CLAUDE Code能正常响应,就说明环境ok了。
2.3 第一个会话:把 pstack 结果交给 Claude 分析
安装好后,先准备一份测试用堆栈文件/tmp/stack.txt,里面可以放一个简化版的pstack输出,比如:
Thread 2 (process 11123): #0 0x00007f1234abcd in __futex_abstimed_wait_private () #1 0x00007f1234abcd in pthread_cond_timedwait@@GLIBC_2.3.2 () #2 0x00007f1234abcd in std::condition_variable::wait_until() #3 0x00007f1234abcd in WorkerQueue::PopWithTimeout() #4 0x00007f1234abcd in WorkerThread::Run() #5 0x00007f1234abcd in start_thread()然后在项目目录里运行:
claude -p "请分析 /tmp/stack.txt 中的线程堆栈,告诉我这个线程在等待什么,可能的问题是什么"-p是Claude Code的非交互打印模式,适合脚本调用,也适合快速的单次问答。你会发现它给出的回答结构相当清晰,会把栈帧按调用链拆开,指出pthread_cond_timedwait说明线程在等条件变量,PopWithTimeout说明这是一个带超时的队列消费,下一步应该去看队列里是否有任务积压。
这种“先手工喂一个文件,再问一句”的方式,就是pstack-claude的雏形。
3. 核心实现:pstack-claude 脚本从零写起
3.1 抓取堆栈前的系统配置
pstack能工作,依赖Linux的ptrace能力,而很多系统的kernel.yama.ptrace_scope默认值是1:意思是进程只能attach到自己的子进程上,普通用户去attach别的用户进程会被拒绝。
排查线上问题时,最简单的方式是用与被排查进程相同的用户去执行pstack,或者直接用sudo:
sudo pstack <pid>如果想临时放开限制,可以改内核参数,但生产环境不建议长期关:
sudo sysctl -w kernel.yama.ptrace_scope=0这个改动重启后就失效,属于应急手段。另外容器环境下还要注意,宿主机可能限制了ptrace系统调用,docker启动参数里如果没给--cap-add=SYS_PTRACE,容器里的pstack也会失败。
3.2 用 pstack 采集进程堆栈
找到目标进程的PID,假设进程名是order-server:
pgrep -f order-server拿到PID之后,直接采集:
pstack 12345 > /tmp/stack-12345.txt不过我自己的经验是:单次pstack往往不够。进程卡死很多时候是有波动的,单拍可能刚好拍到一个正在正常处理任务的线程,漏掉真正阻塞的线程。所以我一般会间隔3秒采样3次,最后合并在一起分析:
for i in 1 2 3; do pstack $PID >> /tmp/stack-$PID.txt echo "--- sample $i ---" >> /tmp/stack-$PID.txt sleep 3 done还有一类特殊情况:如果目标进程是Java应用,pstack其实不太能直接看明白JVM内部的线程状态。这时候应该换jstack命令,它的输出更贴合Java线程模型,包含线程状态(RUNNABLE、BLOCKED、WAITING等)。pstack-claude脚本里最好自动判断进程类型,是Java就调用jstack,否则调用pstack。
3.3 让 Claude Code 自动分析
脚本的核心逻辑就是把“采集堆栈”和“调用Claude分析”串起来。我最初是这么写的:
#!/usr/bin/env bash PID=$1 STACK_FILE="/tmp/pstack-claude-$PID-$(date +%s).txt" if jps -l 2>/dev/null | grep -q "$PID"; then jstack "$PID" > "$STACK_FILE" else pstack "$PID" > "$STACK_FILE" fi claude -p "你是Linux性能诊断专家。请分析以下线程堆栈文件:$STACK_FILE 。要求: 1. 按线程整理调用栈 2. 标出阻塞等待、锁竞争、热点函数 3. 给出下一步排查建议"这个脚本最大的优点是:可重复、可共享。你不需要每次手动复制粘贴堆栈文本,一条命令就能完成从采集到分析的全流程。Claude Code如果配置了默认项目目录,还能自动读取进程命令行、配置文件等额外上下文。
另外一个思路是把分析流程做成Claude Code的Slash Command,放到~/.claude/commands/pstack.md里:
你是Linux进程诊断专家,针对用户提供的pstack或jstack堆栈文件,按线程状态、调用栈特征、锁等待关系给出诊断结论。之后在Claude Code交互界面输入/pstack /tmp/stack-12345.txt,就能触发固定格式的分析,团队里的同学可以共用一套分析模板,输出质量会更稳定。
4. 真实案例分析:一次“进程卡住”的完整排查
4.1 现场还原
有一回我值班,接到告警说某个服务的响应时间从50ms涨到10秒,进程还活着,但看起来明显“卡”。我登录机器先看进程状态:
top -H -p <pid>看到有3个线程CPU占用接近100%,业务日志没有异常,慢查询、GC日志都不像有问题。当时我用pstack采了三份样本,拿到的部分输出长这样:
Thread 12 (process 22123): #0 0x00007f3a123456 in syscall () #1 0x00007f3a123456 in futex_wait () #2 0x00007f3a123456 in __lll_lock_wait () #3 0x00007f3a123456 in pthread_mutex_lock@@GLIBC_2.2.5 () #4 0x00007f3a123456 in ConnectionPool::GetConnection() #5 0x00007f3a123456 in QueryRunner::Execute() #6 0x00007f3a123456 in Handler::Process() Thread 5 (process 22123): #0 0x00007f3a123456 in epoll_wait () #1 0x00007f3a123456 in EventLoop::Run() #2 0x00007f3a123456 in main()人工看一眼,能看到第12线程卡在ConnectionPool::GetConnection,明显是拿不到数据库连接。但为什么拿不到?连接池里的连接去哪了?单凭这一份堆栈说不清楚。
4.2 pstack-claude 给出的诊断
我把三份堆栈合并之后,直接丢给Claude Code,它的回答大概是这样:
分析线程12和线程5:线程5在epoll_wait等于空闲等待,不是阻塞源。线程12栈帧显示从Handler::Process -> QueryRunner::Execute -> ConnectionPool::GetConnection -> pthread_mutex_lock,等待的锁是连接池内部互斥锁。结合三次采样,线程12全部卡在同一个锁上,说明锁的持有时间过长或出现了长时间占用。建议查看连接池最大连接数是否被调低、数据库端是否有慢连接占满连接池,另外检查是否有连接泄漏。
这个结论等于直接帮我锁定了排查方向。它没有说“线程12卡住了”这种废话,而是给出了锁等待的“上游链路”:用户请求进了Handler,到QueryRunner执行SQL,再到连接池获取连接时卡住。于是我去查数据库连接数配置,发现连接池最大连接数被之前同事临时调到了5,而正常请求并发量远高于这个数,连接一满后面的请求全部堵在获取连接这一步。问题就这么定位了。
4.3 对比人工分析的收益
我自己做了个不严谨的对比:
| 步骤 | 人工操作 | pstack-claude |
|---|---|---|
| 理解堆栈内容 | 花5分钟逐行翻译地址、函数名 | 1分钟内输出结构化摘要 |
| 定位阻塞链路 | 需要脑补多个线程的先后关系 | 自动按调用链串起关键线程 |
| 给出下一步建议 | 依赖个人经验积累 | 提供可落地的排查命令和配置方向 |
| 写复盘报告 | 需要单独整理 | 对话结果稍微改就能贴到工单 |
当然,它不是万能的,符号表丢失、堆栈采样不完整的时候,再聪明的模型也得靠人喂上下文。但作为第一轮筛选工具,它的效率提升非常明显。
5. 常见问题与避坑指南
5.1 Claude Code 安装或升级报错排查
报错:auto-update failed: no write permission to npm prefix
这个报错很典型:npm全局包安装目录是root的,当前用户没有写权限,Claude Code想自动更新却写不进去。最简单的解法是把npm全局目录改成用户目录:
mkdir -p ~/.npm-global npm config set prefix ~/.npm-global export PATH=~/.npm-global/bin:$PATH然后重新安装:
npm install -g @anthropic-ai/claude-code之后claude的自动更新就会写到用户目录,不会再碰权限问题。
报错:claude命令找不到
通常是PATH没生效。如果是用npm安装成功但还是找不到命令,先确认全局bin目录在哪个路径,然后把它加到~/.bashrc或~/.zshrc里。
升级失败或者反复提示有新版本
Claude Code版本更新挺勤快的,有时候自动更新会卡住。最简单的方式是手动重新全局安装一次,让它覆盖旧版本:
npm install -g @anthropic-ai/claude-code@latest5.2 pstack 抓不到堆栈怎么办
如果是权限不足,pstack会直接报错Permission denied,这时候用同用户执行或者sudo。另一个常见原因是ptrace_scope限制,前面第3章里已经给出了临时放开的办法。
如果pstack能执行但输出只有地址没有函数名,说明调试符号丢失。C/C++进程需要用带debug信息的二进制,或者安装对应的调试符号包,比如debuginfod支持的情况下可以自动拉取。
还有,有些进程内部用了比较激进的系统调用过滤(seccomp),会直接拒绝ptrace attach,这种情况下pstack无解,只能从系统监控、日志和应用层埋点做侧面判断。
对Java应用,别再对着pstack的空输出发呆,直接上jstack。jstack输出里能看到Java线程状态,比如java.lang.Thread.State: BLOCKED,定位锁问题比pstack直观得多。
5.3 Claude Code 的分析结果不靠谱怎么办
Claude Code再强,也不可能凭一份裸堆栈解决所有问题。我自己遇到过它把一个正常的定时等待当成了死锁,原因是没告诉它业务背景。这时不要急着喷模型,而是给它更多上下文。
可以这样补信息:
claude -p "这是一个Java订单服务的线程转储,服务主要做订单状态流转,最近出现大量请求超时。堆栈文件:/tmp/stack.txt。请重点说明是否有线程持有锁,以及GC线程是否异常"信息越多,分析越准。还有一个技巧是明确要求它“只基于堆栈文件和给出的上下文回答,不要推测你不知道的具体业务逻辑”,能有效减少脑补和幻觉。
如果担心它胡编,可以让它把每一条结论都对应到具体栈帧,比如“哪个线程的哪个调用栈支持这个判断”,这样就能人工快速复核。
最后再分享一点个人体会
pstack-claude这个项目做下来,我最大的感受是:AI编程助手的价值不一定是“替你做决定”,更多是“帮你把原始材料变成可读性更高的中间产物”。读堆栈是个典型的苦活,Claude Code能把几千行十六进制和函数名整理成一份像样的案情摘要,我只需要在摘要上做判断。这套思路不只适用于pstack,像日志分析、配置审计、错误堆栈归因都可以照着这个模式套。建议你也先从自己最痛的那个Linux排查场景开始,写一个最小的脚本,把Claude Code“喂”进去,用起来之后再慢慢加细节。