性能分析工具火焰图
22 人赞同了该文章
火焰图的定义和分类
火焰图整个图形看起来就像一个跳动的火焰,这就是它名字的由来。火焰图有以下特征:
- 每一列代表一个调用栈,每一个格子代表一个函数。纵轴展示了栈的深度,按照调用关系从下到上排列。最顶上格子代表采样时,正在占用 cpu 的函数;
- 横轴的意义是指火焰图采集的多个调用栈信息,通过按字母横向排序的方式将众多信息聚合在一起。(需要注意的是它并不代表时间)例如on-cpu 火焰图的横轴格子的宽度代表其在采样中出现频率,所以一个格子的宽度越大,说明它是瓶颈原因的可能性就越大;
- on-cpu 火焰图格子的颜色是随机的暖色调,方便区分各个调用信息。其他的采样方式也可以使用火焰图, on-cpu 火焰图横轴是指 cpu 占用时间,off-cpu 火焰图横轴则代表阻塞时间。采样可以是单线程、多线程、多进程甚至是多 host。

火焰图
火焰图可以分析函数执行的频繁程度(占用cpu时间,或者阻塞的时间)、可以分析哪些函数经常阻塞、可以分析哪些函数频繁分配内存等分析程序的性能瓶颈。
常见的火焰图类型有 On-CPU、Off-CPU,还有 Memory、Hot/Cold、Differential 等等。它们有各自适合处理的场景。

火焰图类型
| 火焰图类型 | 横轴含义 | 纵轴含义 | 解决问题 | 采样方式 |
|---|---|---|---|---|
| on-cpu火焰图 | cpu占用时间 | 调用栈 | 找出cpu占用高的问题函数,并且分析代码热路径 | 固定频率采样cpu调用栈 |
| off-cpu火焰图 | 阻塞时间 | 调用栈 | IO、网络等阻塞场景导致的性能下降;锁竞争、死锁导致的性能下降问题 | 固定频率采样阻塞事件调用栈 |
| 内存火焰图 | 内存申请/释放函数调用次数 | 调用栈 | 内存泄漏问题;内存占用高的对象/申请内存多的函数;虚拟内存或物理内存泄漏问题 | 有四种方式:跟踪malloc/free;跟踪brk;跟踪mmap;跟踪页错误 |
| Hot/Cold火焰图 | on-cpu和off-cpu综合展示 | 调用栈 | 需要结合cpu占用以及 阻塞分析 的场景;off-cpu火焰图无法直观判断的场景 | on-cpu火焰图和off-cpu火焰图结合 |
| 红蓝分叉火焰图 | 红色表示上升,蓝色表示下降 | 调用栈 | 处理不同版本性能回退问题 | 对比两个on-cpu火焰图 |
如果是 CPU 瓶颈则使用 On-CPU 火焰图 ,(先看cpu是不是快到百分百):

on-cpu火焰图
如果是 IO 或锁的瓶颈则使用 Off-CPU 火焰图 (如果cpu占用率不高,就需要用off-cpu):

off-cpu火焰图
如果无法确定当前的瓶颈到底是什么,可以通过压测工具来确认:
- 通过压测工具看看能否让 CPU 使用率趋于饱和, 如果能那么使用 On-CPU 火焰图。
- 如果不管怎么压, CPU 使用率始终上不来, 那么多半说明程序被 IO 或锁卡住了, 此时适合使用 Off-CPU 火焰图。
如果还是确认不了,可以On-CPU 火焰图和 Off-CPU 火焰图都采集一下,正常情况下它们的差异会比较大, 如果两张火焰图长得差不多, 那么通常认为 CPU 被其它进程抢占 了。
火焰图分析技巧 有:
- 轴代表调用栈的深度(栈桢数),用于表示函数间调用关系:下面的函数是上面函数的父函数。
- 横轴代表调用频次,一个格子的宽度越大,越说明其可能是瓶颈原因。
- on-cpu 火焰图适合分析 cpu 占用高的问题函数 ,off-cpu 火焰图适合解决阻塞和锁抢占问题。
火焰图横向先后顺序是为了聚合,跟函数间依赖或调用关系无关;火焰图各种颜色是为方便区分,本身不具有特殊含义。
生成火焰图
生成和创建火焰图需要如下三个步骤:
- 采集堆栈
- 折叠堆栈
- 生成火焰图

生成火焰图
| 流程 | 描述 | 使用脚本 |
|---|---|---|
| 采集堆栈。采集cpu tick 例如1000HZ | 使用trace工具抓取程序的运行堆栈 | perf/systemtap/dtrace |
| 折叠堆栈。堆栈采集的时间,结束时间等 | trace 工具抓取的系统和程序运行每一时刻的 堆栈 信息, 需要对他们进行分析组合, 将重复的堆栈累计在一起, 从而体现出负载和关键路径 | FlameGraph 中的 stackcollapse 程序 |
| 生成火焰图 (展示信息用的) | 分析 stackcollapse 输出的堆栈信息生成火焰图 | flamegraph.pl |
生成火焰图,需要安装火焰图FlameGraph脚本。 Brendan D. Gregg 的 Flame Graph 工程实现了 一套生成火焰图的脚本 。Flame Graph 项目位于 GitHub上。
https://github.com/brendangregg/FlameGraph
# 使用码云的链接
git clone https://gitee.com/mirrors/FlameGraph.git
# 查看帮助
./FlameGraph/flamegraph.pl -h
不同的 trace 工具抓取到的信息不同, 因此 Flame Graph 提供了一系列的 stackcollapse(堆栈折叠) 工具:
| stackcollapse(堆栈折叠) 工具 | 描述 |
|---|---|
| stackcollapse.pl | for DTrace stacks |
| stackcollapse-perf.pl | for Linux perf_events “perf script” output |
| stackcollapse-pmc.pl | for FreeBSD pmcstat -G stacks |
| stackcollapse-stap.pl | for SystemTap stacks |
| stackcollapse-instruments.pl | for XCode Instruments |
| stackcollapse-vtune.pl | for Intel VTune profiles |
| stackcollapse-ljp.awk | for Lightweight Java Profiler |
| stackcollapse-jstack.pl | for Java jstack(1) output |
| stackcollapse-gdb.pl | for gdb(1) stacks |
| stackcollapse-go.pl | for Golang pprof stacks |
| stackcollapse-vsprof.pl | for Microsoft Visual Studio profiles |
火焰图数据采集工具perf
系统级性能优化通常包括两个阶段: 性能剖析 (performance profiling)和 代码优化 :
- 性能剖析的目标是寻找性能瓶颈,查找引发性能问题的原因及热点代码。
- 代码优化的目标是针对具体性能问题而优化代码或编译选项,以改善软件性能。
一般在工作中比较关心的是性能瓶颈,特别是算法。当在系统全功能启动的时候,算法一般需要将设备的性能用到极限,而在这个过程中不免出现各类性能上的瓶颈。此时需要分析自身的一些性能瓶颈在什么地方,就可以用到专门的性能分析工具 perf 。
perf 命令(performance profiling的缩写), 它是 Linux 系统原生提供的性能分析工具, 会返回 CPU 正在执行的函数名以及调用栈(stack)。
perf的原理是每隔一个固定的时间,就在CPU上(每个核上都有) 产生一个中断 ,在中断上看看,当前是哪个pid,哪个函数,然后给对应的pid和函数加一个统计值,这样就知道CPU有百分几的时间在某个pid,或者某个函数上了:

perf采集
可以1秒采集99次,也可以1秒采集1000次,但是采样次数越高容易影响程序性能。运行时间越多的函数,被时钟中断击中的机会越大,从而推测, 那个函数(或者pid等)的CPU占用率就越高 。
如果某个进程运气特别好,它每次都刚好躲过你发起探测的位置(这时候就需要考虑提高采样频率),统计结果可能就完全是错的了。这是所有采样统计都有可能遇到的问题。
# 安装perf 需要root权限(比如Ubuntu通过sudo su切换到root权限),perf 采集的时候也需要root的权限
apt install linux-tools-common
# 可能还需要安装linux-tools-generic和linux-cloud-tools-generic
apt install linux-tools-generic
apt install linux-cloud-tools-generic
# 测试perf是否可用, 没有报错则在执行的目录产生perf.data
perf record -F 99 -a -g -- sleep 10
# 查看帮助文档
perf -h
perf 常用的5个命令:
- perf list:查看当前软硬件环境支持的性能事件
- perf stat:分析指定程序的性能概况
- perf top:实时显示系统/进程的性能统计信息
- perf record :记录一段时间内系统/进程的性能事件
- perf report :读取perf record生成的perf.data文件,并显示分析数据(生成火焰图用的采集命令)
perf record -h 可以查看perf record命令选项,常用的有:
- -e :指定性能事件(可以是多个,用,分隔列表)
- -p :指定待分析进程的 pid(可以是多个,用,分隔列表)
- -t :指定待分析线程的 tid(可以是多个,用,分隔列表)
- -u :指定收集的用户数据,uid为名称或数字
- - a :从所有 CPU 收集系统数据
- -g :开启 call-graph (stack chain/backtrace) 记录(backtrace调试功能的实现原理就是 利用函数调用栈中的信息来追踪程序执行的路径和调用关系 )
- -C :只统计指定 CPU 列表的数据,如:0,1,3或1-2
- -r :perf 程序以SCHED_FIFO实时优先级RT priority运行。这里填入的数值越大,进程优先级越高(即 nice 值越小)
- -c : 事件每发生 count 次采一次样
- -F :每秒采样 n 次
- -o :指定输出文件output.data,默认输出到 perf.data
perf report -h 可以查看perf report命令选项,常用的有:
- -i, --input input file name,可以指定要分析的文件名,默认perf.data
// 示例代码
#include <stdio.h>
void func_d()
{
for (int i = 5 * 10000; i--;);
}
void func_a()
{
for (int i = 10 * 10000; i--;);
func_d();
}
void func_b()
{
for (int i = 20 * 10000; i--;);
}
void func_c()
{
for (int i = 35 * 10000; i--;);
}
int main(void)
{
printf("main into\n");
while (1)
{
for (int i = 30 * 10000; i--;);
func_a();
func_b();
func_c();
}
printf("main end\n");
return 0;
}
进行编译和执行:
gcc -o test test.c
./test &
# 通过top指令查看test的pid,然后进行perf采集
perf record -F 99 -p 12345 -g -- sleep 30
# perf record 表示采集系统事件, 没有使用 -e 指定采集事件, 则默认采集 cycles(即 CPU clock 周期)
# -F 99 表示每秒 99 次
# -p 12345是进程号, 即对哪个进程进行分析
# -g 表示记录调用栈
# sleep 30 则是持续 30 秒
-F 指定采样频率为 99Hz(每秒99次), 如果 99次 都返回同一个函数名, 那就说明 CPU 这一秒钟都在执行同一个函数, 可能存在性能问题。
运行后会产生一个庞大的文本文件,如果一台服务器有 16 个 CPU, 每秒抽样 99 次, 持续 30 秒, 就得到 47,520 个调用栈, 长达几十万甚至上百万行。
然后可以通过 perf report -n --stdio 命令可以统计每个调用栈出现的百分比, 然后从高到低排列。通过 perf report 命令可以展示采样记录,重要参数如下:
- Samples:采样个数
- Event count:系统总共发生的事件数
- Symbol:函数名,其中 [.] 表示用户空间函数,[k] 表示内核函数
- Shared Objec:函数所在的共享库或所在的程序
- Command:进程名
- Self:该函数的 CPU 使用率
- Children:该函数的子函数的 CPU 使用率
使用 perf script 工具对 perf.data 进行解析,将解析出来的信息存下来, 供生成火焰图。用 stackcollapse-perf.pl 将 perf 解析出的内容 perf.unfold 中的符号进行折叠。
# 生成折叠后的调用栈
perf script -i perf.data &> perf.unfold
# 生成火焰图需要的统计信息
./FlameGraph/stackcollapse-perf.pl perf.unfold &> perf.folded
# 最后生成 svg 图-绘制火焰图
./FlameGraph/flamegraph.pl perf.folded > test_oncpu.svg
# 也可以使用管道将上面的流程简化为一条命令
perf script | ./FlameGraph/stackcollapse-perf.pl | ./FlameGraph/flamegraph.pl > yangshuangxin_oncpu.svg
perf script默认是输入perf.data,如果需要指明输入数据用perf script -i xxx
火焰图的解析
用浏览器打开火焰图进行分析,火焰图是基于 stack 信息生成的 SVG 图片, 用来展示 CPU 的调用栈。
y 轴表示调用栈, 每一层都是一个函数。 调用栈越深, 火焰就越高, 顶部就是正在执行的函数, 下方都是它的父函数。x 轴表示抽样数, 如果一个函数在 x 轴占据的宽度越宽, 就表示它被抽到的次数多,即执行的时间长。
x 轴不代表时间, 而是所有的调用栈合并后, 按字母顺序排列的。
火焰图就是看顶层的哪个函数占据的宽度最大,只要有 “平顶”(plateaus),就表示该函数可能存在性能问题。颜色没有特殊含义, 因为火焰图表示的是 CPU 的繁忙程度, 所以一般选择暖色调。
perf在采样的过程大概分为两步,第一步是调用 perf_event_open 来打开一个 event 文件;第二部是调用 read、mmap等系统调用读取内核采样回来的数据。整体的工作流程图大概如下:

perf采集数据
火焰的每一层都会标注函数名,鼠标悬浮时会显示完整的函数名、抽样抽中的次数、占据总抽样次数的百分比。
在某一层点击,火焰图会水平放大,该层会占据所有宽度,显示详细信息。 左上角会同时显示"Reset Zoom ",点击该链接,图片就会恢复原样。
两种情况下,无法画出火焰图,需要修正系统行为:
- 调用栈不完整 。当调用栈过深时,某些系统只返回前面的一部分(比如前10层)。
- 函数名缺失 。有些函数没有名字,编译器只用内存地址来表示(比如匿名函数)有些函数没有名字,有些函数在编译时被 优化 了,在火焰图上也不会体现。
所有画出正确的火焰图, 先不要做编译优化,很多函数被优化后,是分析不出的 。
off-cpu火焰图
off-cpu火焰图是分析阻塞、锁等问题的,cpu占用率太低无法充分利用cpu的性能。

off-cpu
off-cpu火焰图采集如下:
# 需要在root权限下使能
echo 1 > /proc/sys/kernel/sched_schedstats
# 当阻塞的次数比较多的时候,采集的数据量非常大,所以需要注意采集时间(一般情况30秒左右差不多了)
perf record -e sched:sched_stat_sleep -e sched:sched_switch -e sched:sched_process_exit -p 23509 -g -o perf.data.raw sleep 30
perf inject -v -s -i perf.data.raw -o perf.data
- sched:sched_stat_sleep:进程主动放弃 CPU 而进入睡眠的等待事件
- sched:sched_switch:进程由于I/O和锁等待等原因被调度器切换而进入睡眠的等待事件
- sched:sched_process_exit:进程的退出事件
perf inject -v -s 的含义是:
- -v:表示启用详细模式,会输出更多的调试信息,可帮助排查问题。
- -s:表示显示 symbol 表示。在分析性能数据时,symbol 表示是将地址映射到具体函数或符号名称的过程。
这两个选项主要用于在使用 perf inject 命令时提供更多的调试信息和功能,以便更好地理解和分析性能数据。
# off-cpu生成火焰图
perf script -F comm,pid,tid,cpu,time,period,event,ip,sym,dso,trace | awk '
NF > 4 { exec = $1; period_ms = int($5 / 1000000) }
NF > 1 && NF <= 4 && period_ms > 0 { print $2 }
NF < 2 && period_ms > 0 { printf "%s\n%d\n\n", exec, period_ms }' | \
./FlameGraph/stackcollapse.pl | \
./FlameGraph/flamegraph.pl --countname=ms --title="Off-CPU Time Flame Graph" --colors=io > yangshuangxin_offcpu.svg
发布于 2025-07-23 22:12・浙江
赞同 22