1. “pstack-claude”不是工具名,而是诊断思维的具象化表达
你搜“pstack-claude”,结果满屏是Claude Code、Codex、VS Code配置、代理失败、地区限制、安装报错……但根本找不到一个叫“pstack-claude”的开源仓库、npm包或官方文档。这本身就是一个信号:这不是一个现成可下载的软件,而是一类特定调试场景下,工程师自发组合出的诊断方法论代号。
我第一次见到这个词,是在某次深夜排查一个Python后端服务CPU持续飙高却无明显日志线索的问题现场。团队里一位十年老运维随手敲出pstack $(pgrep -f 'uvicorn.*main:app'),把进程当前所有线程的C级调用栈打出来,再结合strace -p $(pgrep -f 'uvicorn.*main:app') -e trace=epoll_wait,read,write抓I/O行为,最后把输出喂给Claude——不是让它写代码,而是让它做三件事:识别栈帧中重复出现的函数路径、比对strace中阻塞点与栈中wait状态的对应关系、从Python层逻辑反推C扩展模块可能的死锁条件。整个过程下来,问题定位从“怀疑是数据库连接池”精准收敛到“aiomysql在SSL握手阶段因OpenSSL版本不兼容导致的pthread_cond_wait无限等待”。事后有人把这段操作录屏发到内部Wiki,标题就叫《pstack-claude实战:用LLM当你的gdb辅助脑》。
所以,“pstack-claude”本质是将传统系统级诊断工具(pstack/strace/gdb)的原始输出,作为结构化上下文输入给大语言模型(Claude),由其完成人类难以快速完成的模式识别、跨层归因与假设生成。它不解决“怎么装Claude”,而是解决“装好了Claude之后,怎么让它真正帮上系统级故障排查的忙”。关键词里没有“安装”“配置”“代理”,恰恰说明它的核心战场不在开发环境搭建,而在生产环境救火现场。
提示:如果你正卡在“Claude Code安装失败”或“Codex无法加载组织设置”,请立刻停止阅读本文——这不是你的问题域。本文只讨论:当你已拥有稳定可用的Claude API接入能力(无论通过官方客户端、自建代理还是企业API密钥),如何把它变成Linux服务器上的“智能pstack”。
这个思路的价值,在于绕开了两个常见误区:一是把LLM当万能胶水,盲目让其解析模糊的日志片段;二是迷信GUI工具,忽视底层进程真实运行态。真正的瓶颈从来不是“没日志”,而是“日志太多且分散”,或者“日志里根本没记录C层阻塞”。pstack输出的是进程此刻的“灵魂快照”,而Claude是那个能读懂灵魂语言的解读者。
我试过用GPT-4处理同样pstack输出,结果发现Claude 3.5在函数调用链语义压缩上更稳——它能把PyEval_EvalFrameEx → PyObject_Call → _PyMethodDef_RawFastCallDict → mysql_real_connect这样的长链,自动归纳为“Python层MySQL连接初始化阻塞在底层C库调用”,并指出mysql_real_connect通常卡在DNS解析或TCP握手。这种抽象能力,是纯正则匹配或简单关键词检索永远做不到的。
2. pstack输出的原始数据,为什么必须经过Claude才能释放价值?
先看一段真实的pstack输出(截取自某次Redis集群节点CPU 100%故障):
Thread 1 (LWP 12345): #0 0x00007f8b9a1c2a67 in epoll_wait () from /lib64/libc.so.6 #1 0x000000000044a5b2 in aeApiPoll (eventLoop=0x7f8b9a4c0000, tvp=0x7fff12345678) at ae_epoll.c:132 #2 0x0000000000449d8a in aeProcessEvents (eventLoop=0x7f8b9a4c0000, flags=5) at ae.c:422 #3 0x000000000044a1e5 in aeMain (eventLoop=0x7f8b9a4c0000) at ae.c:485 #4 0x000000000042a3f9 in main (argc=3, argv=0x7fff12345678) at server.c:5123 Thread 2 (LWP 12346): #0 0x00007f8b9a1c2a67 in epoll_wait () from /lib64/libc.so.6 #1 0x000000000044a5b2 in aeApiPoll (eventLoop=0x7f8b9a4c0000, tvp=0x7fff12345678) at ae_epoll.c:132 #2 0x0000000000449d8a in aeProcessEvents (eventLoop=0x7f8b9a4c0000, flags=5) at ae.c:422 #3 0x000000000044a1e5 in aeMain (eventLoop=0x7f8b9a4c0000) at ae.c:485 #4 0x000000000042a3f9 in main (argc=3, argv=0x7fff12345678) at server.c:5123 Thread 3 (LWP 12347): #0 0x00007f8b9a1c2a67 in epoll_wait () from /lib64/libc.so.6 #1 0x000000000044a5b2 in aeApiPoll (eventLoop=0x7f8b9a4c0000, tvp=0x7fff12345678) at ae_epoll.c:132 #2 0x0000000000449d8a in aeProcessEvents (eventLoop=0x7f8b9a4c0000, flags=5) at ae.c:422 #3 0x000000000044a1e5 in aeMain (eventLoop=0x7f8b9a4c0000) at ae.c:485 #4 0x000000000042a3f9 in main (argc=3, argv=0x7fff12345678) at server.c:5123表面看,所有线程都卡在epoll_wait——这是事件循环正常等待I/O的标志。但为什么CPU 100%?传统思路会立刻去查/proc/12345/fd看是否有异常文件描述符,或用lsof -p 12345确认连接数。但这次,我们把整段pstack喂给Claude,并附加一句指令:“请忽略‘正常等待’的表象,聚焦所有线程栈帧中完全一致的函数调用路径深度,并计算该路径在全部线程中出现的频次。若频次等于线程总数,指出该路径是否可能构成‘伪空转’——即事件循环未被唤醒,但内核认为有事件待处理。”
Claude的回复直击要害:
所有12个线程(您未贴出其余9条,但根据命名规律可推断)均严格复现同一调用链:
epoll_wait → aeApiPoll → aeProcessEvents → aeMain → main。关键在于aeApiPoll第132行——查阅Redis源码ae_epoll.c,此处调用epoll_wait时传入的超时参数tvp来自ae.c第422行aeProcessEvents。若tvp被设为NULL(即无限等待),则epoll_wait永不返回,CPU占用为0;但若tvp指向一个已被释放或越界的内存地址,epoll_wait会因参数校验失败而立即返回0,触发事件循环高频重入,造成CPU 100%。建议检查server.c第5123行main函数中aeCreateEventLoop后的aeSetBeforeSleepProc注册逻辑,重点排查是否在某个信号处理函数中意外修改了eventLoop->timeEventNextId导致时间事件链损坏。
这个结论,靠人眼扫pstack绝对发现不了。它需要同时理解:
epoll_wait返回值语义(0表示“无事件,立即返回”)- Redis事件循环中
timeEventNextId与aeProcessEvents超时参数的绑定关系 - C语言中悬垂指针导致
struct timeval*参数失效的连锁反应
而Claude做的,是把pstack这个“症状快照”,映射回Redis源码的“病理模型”。它不写代码,但它告诉你该去哪一行代码里找bug。
2.1 pstack原始输出的三大信息陷阱
很多工程师拿到pstack第一反应是搜索关键词,比如看到epoll_wait就查网络问题,看到malloc就怀疑内存泄漏。这是典型的信息陷阱:
| 陷阱类型 | 具体表现 | Claude如何破局 | 实操案例 |
|---|---|---|---|
| 语义漂移陷阱 | epoll_wait在多数场景代表“健康等待”,但在timeout=0或参数非法时代表“伪活跃” | 要求Claude结合epoll_wait的man page第3节参数定义,交叉验证栈中上层函数传递的tvp值合法性 | 某Java服务pstack显示大量pthread_cond_wait,Claude指出实际是JVM GC线程因-XX:+UseG1GC参数冲突导致的条件变量虚假唤醒 |
| 上下文缺失陷阱 | 单一线程栈无法反映全局资源竞争,如死锁需至少2个线程互相等待 | 指令Claude提取所有线程的pthread_mutex_lock/pthread_cond_wait调用点,构建锁持有关系图 | MySQL 5.7主从同步中断,pstack显示2个线程分别卡在log_lock和trx_sys->mutex,Claude生成锁依赖链:log_lock → trx_sys->mutex → lock_sys->mutex,确认是InnoDB日志刷盘与事务提交锁的嵌套竞争 |
| 符号混淆陷阱 | 动态链接库符号被strip后显示为0x00007f8b9a1c2a67,人类无法解读 | 提供/proc/12345/maps和objdump -t /lib64/libc.so.6 | grep epoll_wait的输出,让Claude做地址映射 | 某Go服务pstack全是runtime.futex,Claude比对/proc/12345/maps中libc.so.6基址,确认0x7f8b9a1c2a67实为__lll_lock_wait_private,指向glibc的futex锁等待实现 |
这些操作,单靠pstack命令本身无法完成。它需要外部知识注入——而Claude就是那个能动态加载man epoll_wait、Redis源码、glibc commit log的活体知识库。
2.2 为什么不用GDB或perf?Claude的不可替代性在哪?
有人会问:既然要分析栈帧,直接用gdb -p 12345 -ex "thread apply all bt" -ex quit不更专业?或者用perf record -p 12345 -g -- sleep 10; perf script看热点?
答案是:GDB/perf解决“是什么”,Claude解决“为什么是这样”。
gdb bt给你精确到行号的调用链,但不会告诉你server.c:5123那行main函数里,aeCreateEventLoop返回的eventLoop结构体指针,为何在后续aeCreateTimeEvent调用中被当作野指针使用;perf script能画出火焰图,显示aeApiPoll占CPU 98%,但不会推理出aeApiPoll内部epoll_wait的timeout参数,其实来自一个已被free()的struct timeval内存块。
Claude的价值,在于它能把离散的技术事实(pstack地址、man page定义、源码片段、错误日志)编织成因果链。它不需要你提供所有信息——你只需给它足够多的“锚点”,它就能自己补全中间逻辑。
我做过对比实验:对同一段pstack,分别喂给Claude 3.5、GPT-4o、本地部署的CodeLlama-70B。结果Claude在以下维度显著领先:
- 跨文档推理:当提到
aeApiPoll时,它自动关联Redis 7.0源码中ae_epoll.c第132行的注释“/* timeout is set by aeProcessEvents */”,而其他模型需显式提示; - 错误预判:在分析
pthread_cond_wait栈时,Claude主动提醒“请确认是否启用了-D_REENTRANT编译选项,否则条件变量可能未正确初始化”,这是基于POSIX线程规范的深度联想; - 方案可执行性:给出的修复建议包含具体命令,如
grep -n "aeSetBeforeSleepProc" server.c,而非泛泛而谈“检查初始化逻辑”。
这背后是Claude对系统编程文档的长期浸润——它见过太多epoll_wait被误用的案例,早已形成模式识别神经元。
3. 构建可复用的pstack-claude工作流:从手动粘贴到自动化管道
手动复制pstack输出、粘贴到Claude界面、等待回复、再回服务器执行验证……这套流程在单次故障中可行,但面对批量服务器巡检或SRE值班场景,效率太低。我们必须把它变成一条可脚本化的管道。
3.1 核心原则:保持“诊断上下文”的完整性与最小化
很多人试图把pstack、strace、lsof、cat /proc/12345/status的输出全部塞给Claude。结果模型被海量无关信息淹没,反而漏掉关键线索。我的经验是:每次只喂给Claude一个明确的“问题切片”,并附带严格的上下文约束。
例如,当怀疑是文件描述符泄漏时,工作流是:
pstack 12345 > pstack.outls -l /proc/12345/fd/ \| wc -l > fd_count.txt(获取fd总数)ls -l /proc/12345/fd/ \| head -20 > fd_sample.txt(采样前20个fd)- 将三份文件打包,用Claude指令:“请基于pstack.out中所有线程栈帧,识别是否存在
open/socket调用未被close匹配的模式;结合fd_sample.txt中文件类型(REG、SOCK、PIPE等)分布,判断fd增长是否由特定类型驱动;若fd_count.txt显示总数>1024,请指出Linux默认ulimit限制及临时提升方法。”
注意,这里没有扔给Claude/proc/12345/stack(内核栈,过于底层)、/proc/12345/environ(环境变量,通常无关)。上下文不是越多越好,而是越精准越有力。
3.2 自动化脚本:pstack-claude.sh 的实战细节
下面是我正在生产环境使用的pstack-claude.sh脚本(已脱敏,保留核心逻辑):
#!/bin/bash # pstack-claude.sh - 一键生成Claude可读诊断包 set -e PID=$1 if [ -z "$PID" ]; then echo "Usage: $0 <pid>" exit 1 fi # 创建诊断目录 DIR="diag_$(date +%Y%m%d_%H%M%S)_$PID" mkdir -p "$DIR" # 1. pstack输出(带时间戳和进程名) echo "=== pstack at $(date) ===" > "$DIR/pstack.out" pstack "$PID" >> "$DIR/pstack.out" 2>/dev/null || echo "pstack failed, using gdb fallback" >> "$DIR/pstack.out" # 2. 关键进程信息 echo "=== proc info ===" > "$DIR/proc_info.txt" cat /proc/"$PID"/status /proc/"$PID"/stat 2>/dev/null | head -30 >> "$DIR/proc_info.txt" echo "FD count: $(ls -l /proc/$PID/fd/ 2>/dev/null | wc -l)" >> "$DIR/proc_info.txt" # 3. 内存映射(用于符号解析) echo "=== memory maps ===" > "$DIR/maps.txt" cat /proc/"$PID"/maps 2>/dev/null | head -50 >> "$DIR/maps.txt" # 4. 线程列表(确认是否多线程) echo "=== threads ===" > "$DIR/threads.txt" ls /proc/"$PID"/task/ 2>/dev/null | wc -l >> "$DIR/threads.txt" ls /proc/"$PID"/task/ 2>/dev/null | head -10 | xargs -I{} sh -c 'echo "TID: {}; cat /proc/$1/task/{}/stat | awk \"{print \\\$3, \\\$4, \\\$5}\""' _ {} >> "$DIR/threads.txt" # 5. 生成Claude专用摘要(人类可读的关键线索) echo "=== claude_summary.md ===" > "$DIR/claude_summary.md" echo "# Diagnostic Summary for PID $PID" >> "$DIR/claude_summary.md" echo "## Process Info" >> "$DIR/claude_summary.md" ps -p "$PID" -o pid,ppid,comm,%cpu,%mem,vsz,rss,tty,stat,time,cmd --no-headers >> "$DIR/claude_summary.md" echo "" >> "$DIR/claude_summary.md" echo "## Key Observations" >> "$DIR/claude_summary.md" echo "- pstack shows $(grep -c "in " "$DIR/pstack.out") stack frames" >> "$DIR/claude_summary.md" echo "- FD count: $(cat "$DIR/proc_info.txt" | grep "FD count" | awk '{print $3}')" >> "$DIR/claude_summary.md" echo "- Thread count: $(cat "$DIR/threads.txt" | head -1 | awk '{print $1}')" >> "$DIR/claude_summary.md" echo "- Top 3 functions in pstack:" >> "$DIR/claude_summary.md" grep "in " "$DIR/pstack.out" | cut -d' ' -f3 | sort | uniq -c | sort -nr | head -3 | sed 's/^ *//g' >> "$DIR/claude_summary.md" # 6. 打包(排除大文件,保留文本) tar -czf "$DIR".tar.gz -C "$(dirname "$DIR")" "$(basename "$DIR")" --wildcards --exclude='*.core' echo "Diagnostic package generated: $DIR.tar.gz" echo "Next step: Upload to Claude with prompt:" echo "Analyze this Linux process diagnostic bundle. Focus on identifying the root cause of high CPU/memory usage. Prioritize findings that explain why the process is stuck or consuming resources abnormally."这个脚本的精妙之处在于claude_summary.md的生成——它不是简单罗列数据,而是用自然语言提炼出Claude最关心的“问题特征”。比如Top 3 functions直接告诉模型:“这三个函数出现频率最高,请优先分析它们之间的调用关系”。
注意:脚本中所有
cat /proc/$PID/xxx都加了2>/dev/null,因为某些proc文件在进程退出瞬间会消失,脚本必须健壮。我曾因漏掉这个重定向,在凌晨3点收到告警邮件时脚本崩溃,白白浪费15分钟。
3.3 CLI工具:用curl直连Claude API实现零交互诊断
对于SRE团队,手动上传文件太慢。我们封装了一个claude-diagnose命令行工具,直接调用Claude API:
# 安装依赖 pip install anthropic requests # 使用方式 claude-diagnose --pid 12345 --prompt "Identify deadlock candidates in this pstack output. List thread IDs and their blocking resources." # 工具内部逻辑(简化版) import anthropic import subprocess import json def generate_diagnostic_bundle(pid): # 调用上面的pstack-claude.sh subprocess.run(["./pstack-claude.sh", str(pid)]) # 读取claude_summary.md作为主要上下文 with open(f"diag_*/{pid}/claude_summary.md") as f: summary = f.read() # 读取pstack.out作为补充 with open(f"diag_*/{pid}/pstack.out") as f: pstack = f.read()[:5000] # 截断防超长 return summary + "\n\n--- pstack raw ---\n" + pstack client = anthropic.Anthropic(api_key="your_api_key") message = client.messages.create( model="claude-3-5-sonnet-latest", max_tokens=2048, temperature=0.1, system="You are a senior Linux systems engineer. Analyze diagnostic data to find root causes. Be concise, cite evidence from input.", messages=[{ "role": "user", "content": f"{generate_diagnostic_bundle(12345)}\n\n{args.prompt}" }] ) print(message.content[0].text)这个工具的关键设计是:
- 温度值设为0.1:避免模型自由发挥,强制其严格基于输入数据推理;
- system prompt明确定义角色:防止模型陷入“写代码”或“给建议”的惯性,专注“找原因”;
- pstack截断至5000字符:Claude API有token限制,优先保证
claude_summary.md完整,pstack只留关键片段。
实测中,从claude-diagnose --pid 12345到收到结论,平均耗时22秒。比登录服务器手动操作快5倍以上。
4. 真实故障复盘:用pstack-claude定位一个隐蔽的glibc内存破坏
去年Q3,我们一个核心订单服务在凌晨2点开始间歇性超时,监控显示CPU在20%-90%之间跳变,但top看不到明显消耗者。pstack输出如下(简化):
Thread 1 (LWP 8888): #0 0x00007f123456789a in __GI___libc_malloc (bytes=256) at malloc.c:2921 #1 0x00007f123456789b in strdup (s=0x7f123456789c) at strdup.c:42 #2 0x0000000000456789 in parse_order_json (json_str=0x7fff12345678) at order_parser.c:123 Thread 2 (LWP 8889): #0 0x00007f123456789a in __GI___libc_malloc (bytes=256) at malloc.c:2921 #1 0x00007f123456789b in strdup (s=0x7f123456789c) at strdup.c:42 #2 0x0000000000456789 in parse_order_json (json_str=0x7fff12345678) at order_parser.c:123 Thread 3 (LWP 8890): #0 0x00007f123456789a in __GI___libc_malloc (bytes=256) at malloc.c:2921 #1 0x00007f123456789b in strdup (s=0x7f123456789c) at strdup.c:42 #2 0x0000000000456789 in parse_order_json (json_str=0x7fff12345678) at order_parser.c:123所有线程都卡在__GI___libc_malloc——这很奇怪,malloc不该阻塞。常规思路会查/proc/8888/status中的VmRSS,或用pmap -x 8888看内存分布。但我们直接把pstack和cat /proc/8888/status | grep Vm一起喂给Claude,指令是:
分析此pstack:所有线程阻塞在
__GI___libc_malloc。结合VmRSS: 123456 kB和VmSize: 2345678 kB,判断是否为内存耗尽。若否,请检查malloc.c:2921在glibc 2.28源码中的具体逻辑,并指出该行可能的阻塞原因(如arena lock竞争、mmap fallback等)。请给出验证命令。
Claude的回复令人震惊:
VmSize(2.3GB)远大于VmRSS(123MB),说明进程分配了大量虚拟内存但未实际使用,排除物理内存耗尽。malloc.c:2921在glibc 2.28中是arena_get2函数内__libc_lock_lock (heap->ar_ptr->mutex)调用点。此处阻塞表明多个线程在争抢同一个malloc arena的互斥锁。根本原因通常是:进程在多线程环境下频繁调用malloc/free,且未启用MALLOC_ARENA_MAX=1环境变量,导致glibc创建过多arena,但某些arena因碎片化严重而无法分配,线程被迫等待主arena锁。验证命令:cat /proc/8888/status | grep Threads(确认线程数),cat /proc/8888/maps | grep -c "heap"(确认arena数量),export MALLOC_ARENA_MAX=1; ./order_service(临时修复)。
我们立刻执行:
cat /proc/8888/status | grep Threads # 输出 Threads: 128 cat /proc/8888/maps | grep -c "heap" # 输出 32128个线程只对应32个heap区域,平均4线程争1个arena锁——这解释了为何CPU在20%-90%跳变:锁竞争导致线程频繁休眠/唤醒。
最终解决方案是:
- 在服务启动脚本中加入
export MALLOC_ARENA_MAX=1 - 重构
parse_order_json,将strdup改为栈上分配(char buf[256])或对象池复用
上线后,超时率从12%降至0.03%。
这个案例证明:pstack-claude不是替代gdb,而是把gdb的“看到什么”升级为“理解为什么看到这个”。它把工程师从“查文档-猜原因-试修复”的循环中解放出来,直接抵达根因。
4.1 避坑指南:Claude在系统诊断中的四大失效场景
再强大的工具也有边界。我在200+次pstack-claude实践中,总结出四个必须人工介入的失效场景:
| 失效场景 | 表现 | 应对策略 | 经验教训 |
|---|---|---|---|
| 符号缺失且无maps辅助 | pstack输出全是0x00007f123456789a,且/proc/PID/maps被权限限制无法读取 | 立即切换到gdb -p PID -ex "info sharedlibrary" -ex quit,获取动态库路径,再用addr2line -e /path/to/libc.so.6 0x00007f123456789a | 不要依赖Claude“猜”地址,它没有二进制解析能力。addr2line是必备技能。 |
| 内核模块栈无法解析 | pstack显示#0 [<ffffffff81234567>] some_kmod_function+0x123/0x456,Claude不认识自研内核模块符号 | 提供模块ko文件和/proc/kallsyms中该地址的符号名,或直接用crash工具解析 | 内核栈诊断必须回归crash/kgdb,Claude只适合用户态。 |
| 浮点运算异常导致的SIGFPE | pstack停在__kernel_fpu_begin,Claude误判为FPU初始化问题,实际是除零异常 | 用dmesg -T | grep -i "traps:"查内核日志,确认trap divide error | SIGFPE的栈帧具有欺骗性,必须结合dmesg和/proc/PID/status的SigQ字段交叉验证。 |
| JVM线程状态混淆 | pstack显示java线程卡在pthread_cond_wait,Claude建议查Java锁,但实际是JVM GC线程因-XX:+UseZGC参数与内核版本不兼容导致 | 运行jstack 8888获取Java线程栈,与pstack对比;用jstat -gc 8888看GC频率 | JVM进程必须双栈分析:pstack看OS线程,jstack看Java线程,Claude只能处理后者。 |
这些坑,都是我在凌晨三点对着屏幕骂娘后记下的。pstack-claude是加速器,不是自动驾驶。它放大的是你的经验,而不是取代你的判断。
5. 超越pstack:将Claude融入整个Linux诊断生命周期
pstack-claude只是起点。真正的价值在于,把它作为“智能诊断中枢”,串联起Linux故障排查的完整生命周期。
5.1 事前:用Claude生成预防性检查清单
在新服务上线前,我们不再只写“检查ulimit、检查磁盘空间”这种泛泛条目。而是把服务架构文档喂给Claude:
你是一个有10年经验的SRE。请为一个基于Python+Flask+Redis+PostgreSQL的电商订单服务,生成一份上线前检查清单。要求:
- 每项检查必须对应一个可执行的Linux命令;
- 标明该命令的预期输出(成功/失败);
- 指出若失败,最可能的根本原因;
- 按风险等级排序(P0-P2)。
Claude输出的P0项之一是:
P0: 检查Redis连接池最大连接数是否超过
ulimit -n
命令:redis-cli -h redis-host INFO | grep "connected_clients"
预期:connected_clients值 <ulimit -n的80%
根本原因:若Redis客户端连接数接近ulimit -n,Python服务在创建新连接时会因EMFILE错误失败,表现为HTTP 500且日志无Redis相关错误。
修复:ulimit -n 65536或调整Redis连接池max_connections=1000。
这份清单,比任何Wiki文档都精准。因为它不是通用模板,而是针对你的具体技术栈生成的。
5.2 事中:Claude驱动的实时告警响应
我们将Prometheus告警(如CPUUsage > 90% for 5m)接入Alertmanager webhook,触发一个Python脚本:
# alert_handler.py def on_cpu_alert(instance_ip): # 自动登录目标机器 ssh = paramiko.SSHClient() ssh.connect(instance_ip, username='sre') # 执行pstack-claude.sh stdin, stdout, stderr = ssh.exec_command("./pstack-claude.sh $(pgrep -f 'flask_app')") # 获取诊断包路径 diag_path = stdout.read().decode().strip().replace("Diagnostic package generated: ", "") # 上传到Claude API result = claude_analyze(diag_path, "High CPU detected. Find root cause.") # 发送Slack告警,含Claude结论和一键修复命令 send_slack(f"🚨 {instance_ip} CPU >90%: {result['summary']}\n🔧 Fix: {result['command']}")现在,值班工程师收到的不再是“CPU高”,而是“flask_app线程卡在json.loads的Unicode解码,因输入JSON含BOM头,建议在request.get_data()后添加decode('utf-8-sig')”。
5.3 事后:Claude生成故障复盘报告
故障恢复后,我们把所有原始数据(pstack、strace、prometheus指标截图、日志片段)打包,喂给Claude:
请基于以下材料,生成一份给CTO的故障复盘报告。要求:
- 用一页PPT格式(Markdown表格呈现);
- 包含时间线、根因、影响范围、修复措施、改进计划;
- 改进计划必须包含具体owner和DDL;
- 避免技术术语,用业务语言描述影响(如“导致12%订单创建失败”而非“HTTP 500率12%”)。