3. perf record与perf report
perf record 和 perf report 是 Linux perf 子系统中使用频率最高的一对搭档。perf record 负责在目标程序运行时采集性能数据,perf report 则负责将采集到的原始数据解析、汇总并呈现为可读的分析报告。理解它们的采样原理、参数含义以及如何生成火焰图和调用链,是进行系统性能分析的基础。
核心概念:perf record 基于硬件 PMU(Performance Monitoring Unit)或软件事件进行采样。默认模式下,它按照固定频率(如每 1ms)触发中断,记录当前 CPU 的指令指针(IP)和调用栈。perf report 则对这些样本进行统计,生成热点分布和调用关系。
3.1 采样原理:从 PMU 到样本
perf 的采样机制依赖于 CPU 内部的 PMU 计数器。当计数器溢出时,CPU 触发一个 NMI(Non-Maskable Interrupt),perf 内核子系统在中断处理函数中记录当前上下文信息。
采样过程的关键步骤:
- 配置计数器:perf 根据用户指定的事件(如 cycles、instructions)和采样周期(period 或 frequency)配置 PMU 寄存器。
- 运行目标程序:目标程序在 CPU 上正常执行,PMU 计数器同步递增。
- 计数器溢出:当计数器达到预设阈值时,触发 PMU 中断。
- 记录样本:perf 中断处理函数保存当前 RIP(指令指针)、PID、TID、CPU ID、调用栈(通过帧指针或 dwarf unwind)到内核缓冲区。
- 数据输出:perf record 进程定期从内核缓冲区读取数据,写入 perf.data 文件。
采样 vs 插桩:采样(Sampling)是统计性的,开销低,适合分析长时间运行的程序;插桩(Instrumentation)如 ftrace 或 kprobe 是确定性追踪,开销高但能捕获每次事件。perf record 默认使用采样模式。
3.2 perf record:记录性能数据
perf record 是数据采集的入口。它的核心职责是:在目标程序运行期间,按照指定的采样频率或事件周期,将性能样本写入 perf.data 文件。
3.2.1 常用参数
| 参数 | 含义 | 示例 |
|---|---|---|
-e |
指定性能事件,如 cycles、instructions、cache-misses | -e cycles,instructions |
-F |
采样频率(Hz),每秒采集样本数 | -F 99 |
-c |
采样周期,每发生 N 次事件采集一次 | -c 100000 |
-g |
记录调用栈(call-graph) | -g |
-p |
附加到指定 PID 的进程 | -p 12345 |
-a |
全系统采样(system-wide) | -a |
--call-graph |
指定调用栈采集方法:fp(帧指针)或 dwarf | --call-graph dwarf |
-o |
指定输出文件名,默认 perf.data | -o myperf.data |
3.2.2 典型使用场景
场景一:分析 CPU 热点
# 以 99Hz 采样 CPU 周期事件,记录调用栈
perf record -e cycles -F 99 -g -- ./myapp
# 采样 10 秒后自动停止
perf record -e cycles -F 99 -g -- sleep 10 ./myapp
场景二:分析缓存缺失
# 采集 cache-misses 事件
perf record -e cache-misses -F 99 -g -a -- sleep 30
场景三:分析指定进程
# 附加到运行中的 nginx 进程
perf record -e cycles -F 99 -g -p $(pgrep -x nginx) -- sleep 10
注意事项:
- 采样频率过高(如 -F 10000)可能导致采样开销过大,影响目标程序行为。
- 使用
-g时,如果程序未编译帧指针(-fomit-frame-pointer),建议使用--call-graph dwarf,但 dwarf 展开速度较慢。 - 全系统采样(-a)需要 root 权限。
3.3 perf report:生成火焰图与调用链分析
perf report 读取 perf.data 文件,将原始样本解析为可读的报告。它支持多种输出格式,包括文本报告、调用链树、以及用于生成火焰图的数据。
3.3.1 基本使用
# 查看默认报告
perf report
# 以调用链形式显示
perf report -g graph
# 输出到文件
perf report -g graph --stdio > report.txt
3.3.2 调用链分析
perf report 的调用链视图展示了每个热点函数的调用路径。默认按 overhead(开销占比)排序,从高到低显示。
调用链的两种显示模式:
| 模式 | 说明 | 适用场景 |
|---|---|---|
graph |
以树形结构显示每个函数的调用链,子函数缩进 | 快速定位热点路径 |
fractal |
类似 graph,但每个函数只显示一次,子调用折叠 | 减少重复,适合大型调用链 |
flat |
扁平显示,不展开调用链 | 只看函数自身开销 |
3.3.3 生成火焰图数据
火焰图(Flame Graph)是 Brendan Gregg 发明的可视化工具,用于直观展示 CPU 热点分布。perf 本身不直接生成 SVG 火焰图,但可以输出折叠格式的调用栈数据,供 FlameGraph 工具集使用。
生成火焰图的步骤:
- 采集数据:perf record -e cycles -F 99 -g -- ./myapp
- 生成折叠栈:perf script > out.perf
- 折叠调用栈:stackcollapse-perf.pl out.perf > out.folded
- 生成 SVG:flamegraph.pl out.folded > flame.svg
# 一键生成火焰图数据(使用 perf 内置脚本)
perf script -i perf.data --header | ./stackcollapse-perf.pl | ./flamegraph.pl > flame.svg
3.4 实战案例:分析 CPU 热点并生成火焰图
以下是一个完整的实战流程,演示如何从采样到生成火焰图。
步骤 1:编写测试程序
// test_hot.c
#include <stdio.h>
#include <stdlib.h>
void hot_function() {
volatile int i;
for (i = 0; i < 100000000; i++);
}
void cold_function() {
volatile int i;
for (i = 0; i < 1000; i++);
}
int main() {
int i;
for (i = 0; i < 10; i++) {
hot_function();
cold_function();
}
return 0;
}
步骤 2:编译并采样
gcc -g -O0 -fno-omit-frame-pointer -o test_hot test_hot.c
# 采样 5 秒
perf record -e cycles -F 99 -g -- ./test_hot
步骤 3:查看报告
perf report -g graph --stdio
# 输出片段示例:
# 99.99% test_hot test_hot [.] hot_function
# |
# --99.99%-- main
# hot_function
步骤 4:生成火焰图
# 安装 FlameGraph 工具
git clone https://github.com/brendangregg/FlameGraph.git
cd FlameGraph
# 生成折叠栈
perf script -i ../perf.data | ./stackcollapse-perf.pl > out.folded
# 生成 SVG 火焰图
./flamegraph.pl out.folded > flame.svg
火焰图解读:
- X 轴:样本分布,不代表时间顺序;宽度越宽,表示该函数占用的 CPU 时间越多。
- Y 轴:调用栈深度,底部是根调用,顶部是叶子函数。
- 颜色:通常随机分配,用于区分不同函数;也可以按函数名哈希着色。
- 鼠标悬停:显示函数名和样本占比。
3.5 常见问题与避坑
| 问题 | 原因 | 解决方案 |
|---|---|---|
perf report 显示 ?? 符号 |
缺少符号表或调试信息 | 编译时加 -g,或安装 debuginfo 包 |
| 调用链不完整 | 帧指针被优化掉,或 dwarf 展开失败 | 使用 --call-graph dwarf,或编译时加 -fno-omit-frame-pointer |
| 采样开销过高 | 采样频率设置过高 | 降低 -F 值,或使用 -c 指定周期 |
| perf.data 文件过大 | 采样时间过长或频率过高 | 使用 --time 限制采样时间,或增大采样周期 |
| 火焰图显示所有函数名相同 | perf script 输出未正确折叠 | 检查 stackcollapse-perf.pl 是否处理了多线程 |
最佳实践:
- 首次分析使用
-F 99(99Hz),兼顾精度和开销。 - 始终使用
-g记录调用链,否则无法生成火焰图。 - 生产环境建议使用
--call-graph fp并确保程序编译时保留帧指针。 - perf.data 文件包含原始样本,可以离线传输到分析机器上使用 perf report 查看。