perf script 输出不能直接喂给 flamegraph.pl,因其输出为原始采样事件流,而 flamegraph.pl 仅接受折叠格式(每行一个分号分隔的调用栈+次数),否则生成空白或错误 svg。

perf script 输出为什么不能直接喂给 flamegraph.pl?
因为 perf script 默认输出的是原始采样事件流(含时间戳、PID、符号地址、调用栈等杂项),而 flamegraph.pl 只认“折叠后”的调用栈格式:每行一个栈,函数名用分号分隔,末尾是抽样次数。不折叠就生成空白或报错 SVG。
- 常见错误现象:
flamegraph.pl out.perf-script生成的 SVG 里全是空白,或者只有一条扁平的「[unknown]」横条 - 真正该喂进去的是
stackcollapse-perf.pl处理后的文件,比如out.perf-folded - 如果你跳过折叠步骤,
flamegraph.pl会把每一行当成一个独立函数,完全丢失调用关系
怎么写一个安全可用的自动化脚本?
核心就三步:record → script → fold → svg,但必须加容错和路径控制,否则在 CI 或多用户环境容易炸。
- 用
perf record -g -F 99 -p $PID -- sleep 10比裸跑命令更稳,避免因进程提前退出导致perf.data不完整 -
perf script后建议加2>/dev/null,某些内核版本会在 stderr 吐 warning 干扰管道 - 折叠前先检查
/tmp/perf-$PID.map是否存在(Node.js 等 JIT 语言需要它还原符号),缺失时火焰图里会出现大量[jitted]或十六进制地址 - 生成 SVG 时用绝对路径写入,比如
$(pwd)/flamegraph-$(date +%s).svg,防止同名覆盖或权限问题
为什么 -g 参数漏掉就白忙活?
-g 是开启调用图(call graph)采集的开关,没它 perf record 只记下当前 CPU 正在执行的函数,不记录“是谁调的它”。没有调用栈,火焰图就只剩一层——根本看不出瓶颈在哪儿。
- 错误示范:
perf record -e cycles -p 1234→ 火焰图所有函数都悬浮在底部,无父子关系 - 正确写法必须带
-g,且推荐搭配-F 99(不是越高越好,1000Hz 在高负载机器上可能失真) - 注意:
-g依赖内核的 frame pointer 支持;若程序用-fomit-frame-pointer编译(如很多 Go 程序),需改用--call-graph dwarf,但开销更大
生产环境跑这个脚本要注意什么?
perf 本身是轻量采样,但连续高频采集 + 生成 SVG 仍可能短暂推高 CPU 和磁盘 IO,尤其在容器或低配实例上。
- 别在高峰期对关键进程跑
sleep 30,用--duration 5快速抓点(BCC 的profile工具更适合长期监控) - 生成的
perf.data文件默认存当前目录,大应用采样 10 秒可能超百 MB,记得rm -f perf.data清理 - 如果目标进程是 Java,得额外加
-e cpu-clock,java:vm:gc等事件,单纯cpu-clock会漏掉 safepoint 停顿
真正的坑不在命令写错,而在你信了“一次采样全看清”——火焰图只反映 CPU 时间分布,IO 等待、锁竞争、GC 暂停这些非 CPU 开销,得换 off-cpu 火焰图或 perf lock 等专项工具。










