perf record + perf report 或 perf script 配合火焰图是定位 CPU 热点函数最可靠的方式;perf top 仅适合快速现场扫描,不可复现、不可追溯,且需注意权限、内核配置、调用栈完整性及结果解读陷阱。

直接上结论:perf record + perf report 或 perf script 配合火焰图,是定位 CPU 热点函数最可靠的方式;perf top 只适合快速现场扫描,不能复现、不可追溯。
确认权限和内核配置是否就绪
perf 依赖内核的 perf_events 子系统,很多生产环境默认禁用高精度采样。必须先检查并修复基础配置:
-
sudo sysctl kernel.perf_event_paranoid返回值应 ≤ 1(常见安全加固会设为 2 或 3);若大于 1,执行sudo sysctl -w kernel.perf_event_paranoid=-1 - 确认当前内核版本已启用 perf 支持:
zcat /proc/config.gz | grep CONFIG_PERF_EVENTS(若无/proc/config.gz,查/boot/config-$(uname -r)) - 非 root 用户无法采样用户态栈或内核函数,所有
perf record/perf top命令必须加sudo
对指定进程采样并保留完整调用栈
只用 -g 参数容易丢栈——尤其 C++/Rust/Go 程序中内联或尾调用频繁时。推荐明确指定调用图解析方式:
- 优先用
--call-graph dwarf:从二进制中读取 DWARF 调试信息还原栈,准确率最高,但要求程序或符号包含调试信息(如ceph-osd-dbg) - 备选
--call-graph fp(frame pointer):依赖编译时未关闭-fno-omit-frame-pointer,C/C++ 默认开启,Go/Rust 需显式加编译选项 - 避免仅用
-g:它在新内核上默认 fallback 到fp,但不报错,实际采不到深栈却以为“已采集” - 示例命令:
sudo perf record -p $(pidof ceph-osd) -F 99 --call-graph dwarf -- sleep 30
解读 perf report 输出的关键陷阱
perf report 文字报告里最容易误判的不是“哪个函数占比高”,而是“这个占比属于用户态还是内核态、属于当前进程还是被调度抢占”:
- 看第一列符号前缀:
[.]表示用户态函数,[k]表示内核函数,[g]是内核 guest 模式(虚拟机中);混在一起时,[k]占比突增往往意味着锁竞争或 I/O 等待 - 按
→键展开调用链后,注意每层右侧的百分比是“该帧在全部样本中出现的比例”,不是“占上一层的比例”——所以顶层函数总和可能远超 100% - 若看到大量
__libc_start_main或start_thread占比异常高,大概率是采样频率太低(如用了-F 10)或进程刚启动/即将退出,样本集中在启动/清理路径 - 运行
perf report -n可显示各符号的原始采样次数,比百分比更抗干扰
生成火焰图前必须过滤无关上下文
直接拿 perf script 输出喂给 FlameGraph 脚本,常得到一堆 sys_read、do_syscall_64 这类通用内核路径,掩盖真实业务热点:
- 先过滤出目标进程的用户态栈:
sudo perf script -F comm,pid,tid,cpu,time,period,event,ip,sym,cycles,iregs -F callindent=2 | ./stackcollapse-perf.pl | ./flamegraph.pl > osd-flame.svg - 若只想看用户态(排除所有
[k]):perf script | awk '$5 != "[k]" {print}' | ./stackcollapse-perf.pl | ./flamegraph.pl - 火焰图横轴不代表时间轴,而是“所有样本合并后的栈频次排序”,所以窄而高的尖峰比宽而矮的平台更值得优先排查
- 注意:火焰图无法反映函数执行时长分布(比如一个函数平均耗时 1ms,但 99% 样本落在最后 10μs),它只回答“CPU 时间集中在哪条调用路径上”
真正难的不是跑通命令,而是判断“这个热点是真瓶颈,还是只是高频但低延迟的常规路径”。比如 bluestore::onread 在 OSD 中占比 35%,得结合 iostat 和 ceph tell osd.x perf dump 确认是磁盘慢导致反复重试,还是函数本身有算法缺陷——perf 告诉你“在哪”,但“为什么在这”还得靠交叉验证。



















