perf record/report 采样与火焰图完整流程
更新时间:2026-09-19。这是本站看家方向的核心工作流。perf stat 给聚合计数,perf record 用定时采样回答「CPU 时间具体烧在哪个函数、哪条调用链」,再用火焰图把调用栈可视化。命令速查见 perf,原理见 perf 内部机制。
一、采集:perf record
# 对运行中的进程采样,99Hz,记录调用栈
perf record -F 99 -a -g -- sleep 30 # -a 全系统 30 秒
perf record -F 99 -p $(pidof app) -g -- sleep 30 # 只采某进程
perf record -F 99 -g ./prog --args # 启动并采样
perf record -e cpu-clock -F 99 -g -p PID # 无硬件 PMU 的虚拟机用软件事件| 参数 | 作用 |
|---|---|
-F 99 | 每秒采样 99 次(用 99/101 而非整百,避免和周期任务对齐产生偏差) |
-g | 记录调用栈(栈回溯) |
-p / -a | 指定进程 / 全系统 |
-o file.data | 指定输出(默认 perf.data) |
-- sleep N | 采样时长 |
每次采样记录「此刻哪个 CPU 在执行哪个栈」,采样数近似等于 CPU 时间占比(on-CPU 剖析:只统计正在占用 CPU 的时间)。
二、栈回溯:帧指针 / dwarf / lbr
调用栈采不全(只剩顶层函数)是最常见问题:
perf record -F 99 -g --call-graph dwarf -p PID # 用 DWARF 调试信息回溯(兼容性好,需 -fno-omit-frame-pointer 或调试信息)
perf record -F 99 -g --call-graph lbr -p PID # 用硬件 Last Branch Record,开销低、深度有限
perf record -F 99 -g --call-graph fp -p PID # 帧指针(需编译保留 -fno-omit-frame-pointer)- 现代编译器默认省略帧指针(
-fomit-frame-pointer),光用-g栈可能断;优先--call-graph dwarf(二进制带调试信息)或 lbr。 - Java/Go/JIT 语言需要额外的符号/代理支持(如 perf-map-agent、JFR),否则栈是地址。
三、分析:perf report
perf report # 交互界面:函数热度、调用链展开
perf report --stdio | less # 纯文本,便于检索
perf report -g graph,0.5,caller # 调用者视角火焰式聚合看 Overhead 列:占比最高的函数/调用链就是 CPU 热点。注意区分:
- 自开销(Self):函数自己烧的时间(真正的计算热点)。
- 包含开销:它调用的子函数的时间。优化点通常在自开销高、或被频繁调用的叶子函数。
四、生成火焰图
# 方式一:Brendan Gregg FlameGraph 工具
perf script -i perf.data > out.stacks
/path/FlameGraph/stackcollapse-perf.pl out.stacks > out.folded
/path/FlameGraph/flamegraph.pl out.folded > flame.svg
# 一行管道
perf script | stackcollapse-perf.pl | flamegraph.pl > flame.svg
# 方式二:Inferno(Rust,更快,可直接吃 perf.data)
inferno-collapse-perf perf.data | inferno-flamegraph > flame.svg火焰图怎么读
每个矩形=一个栈帧;X 轴宽度=采样到的 CPU 时间占比(不是时间顺序!)
Y 轴=调用深度(下面是父,上面是子)
最宽的平顶函数 ≈ CPU 热点;点击可放大,Ctrl-F 搜索高亮关键认知:火焰图 X 轴是聚合后的占比,不是从左到右的时间线。找「最宽的平台」而非「尖峰」。常见形态:
- 某个函数自身又宽又平顶 → 直接优化它(算法/缓存/SIMD)。
- 大量宽度堆在 memcpy/锁/内存分配 → 数据拷贝/锁竞争/分配器问题。
- 看起来「高但每层都窄」是深调用链,未必是热点,以宽度为准。
五、on-CPU 之外
perf record 默认看的是「在 CPU 上忙」的时间。如果程序慢是因为等 IO、锁、调度(on-CPU 很闲但请求慢),on-CPU 火焰图看不到,要做 off-CPU 剖析:用 sched:sched_switch 跟踪睡眠时长,或用 bcc 的 offcputime、bpftrace。先用 vmstat/runqlat 判断是「忙」还是「等」,再选工具。
六、符号与环境
- 看不到函数名全是地址:装对应
-dbgsym/-dbuginfo,二进制保留符号(勿 strip)。 - 容器内采样需宿主权限与 PID 命名空间映射(
--pid=container、root/CAP_PERFMON)。 /proc/sys/kernel/perf_event_paranoid控制权限(生产常需临时设为 -1/0)。- 频率不是越高越好:
-F 99开销通常 <2%,几千 Hz 在生产会明显扰动结果并可能被限流(看 lost 事件)。
坑
- 不做栈回溯(漏
-g/帧指针丢失),只看到叶子函数找不到调用来源。 - 把火焰图横轴当时间线找「尖峰」,实际要找最宽的聚合平台。
- 用 on-CPU 火焰图排查锁/IO 等待类慢请求,方向就错了,应做 off-CPU。
- 生产用超高采样率,扰动大且事件丢失。
- JIT 语言没装符号代理,栈全是十六进制地址无法解读。
总结
perf record -F99 -g 以固定频率对进程或全系统采样 on-CPU 调用栈,帧指针被省略时用 --call-graph dwarf 或 lbr 保证栈完整,再用 perf report 按自开销/包含开销找热点、或经 perf script 加 stackcollapse/flamegraph(或 Inferno)生成火焰图;读图时记住 X 轴宽度代表 CPU 占比而非时间、找最宽平台而非尖峰;而等 IO、锁、调度造成的慢属于 off-CPU,需要 sched_switch 或 bcc/bpftrace 单独剖析,并在生产注意符号、权限和采样频率开销。