使用火焰图(FlameGraph)分析程序性能
使用火焰图(FlameGraph)分析程序性能
火焰图(FlameGraph)是一种用于可视化程序性能分析结果的工具,通过SVG格式的矢量图直观展示函数调用栈的深度和执行时间。本文将详细介绍火焰图的概念、类型、生成方法及其在不同场景下的应用,帮助开发者快速定位程序性能瓶颈。
火焰图概念
火焰图(FlameGraph)是SVG格式的矢量图,是先通过perf等工具分析得到结果,并将该结果生成的具有不同层次且支持互动的图片,看起来就像是火焰,这也是它的名字的由来。表现形式如下所示:
需要注意以下几点:
- 纵向(Y轴)高低不平,表示的是函数调用栈的深度。每一层都是一个函数。调用栈越深,火焰就越高,顶部就是正在执行的函数,下方都是它的父函数。
- 横向(X轴)表示该函数执行消耗的时间,横向上会按照字母顺序排序,而且如果是同样的调用会做合并(注意:如果一个函数在X轴占据的宽度越宽,就表示它被抽到的次数多,即执行的时间长,所以这里不是严格意义上的执行消耗的时间),所以一个横向宽度越大的函数调用,一般很可能是程序的瓶颈。
- 火焰图的颜色是随机分配的,并不是颜色越深就是越瓶颈。因为火焰图表示的是CPU的繁忙程度,所以一般都是暖色调。我们需要留意的就是那些比较宽大的火苗。只要有"平顶",就表示该函数可能存在性能问题。
要生成火焰图,就需要一个Tracer软件,通过该软件在系统上的运行过程采样,得到结果,并将该结果图形化,才可得到人眼直观可视化的SVG格式数据矢量图。
在Linux系统上,通常选择的是perf或者systemtap,但是perf更为常用,因为它是Linux内核内置的性能调优工具,大多数Linux都包含了该工具。而systemtap虽然更强大,但你需要先学习它本身的编程语言。
On/Off-CPU火焰图
火焰图的类型有:On-CPU、Off-CPU,Memory等,可以参考官方以及Blazing Performance with Flame Graphs。
什么时候用On-CPU火焰图,什么时候才用Off-CPU火焰图呢?这取决于程序的瓶颈是什么:
- 如果是CPU则使用On-CPU火焰图。
- 如果是IO或锁则使用Off-CPU火焰图。
- 如果不能确定,则需要使用压力测试工具来确认一下:在压测下,如果CPU使用率趋于饱和,则使用On-CPU火焰图。如果无论怎么压,CPU使用率始终不饱和,说明程序中存在IO或锁的瓶颈,这是就适合用Off-CPU火焰图。
实在不行,就两个都试试,正常情况下,两个差异会比较大。但如果差异不大,通常认为CPU被其他进程抢占了。需要注意的是,采样数据需要通过压测工具对程序持续施压,以便采集到足够的样本,关于压测工具选择,如果选择apache bench的话,务必开启-k选项,以避免系统可用端口耗尽,也可以尝试如wrk之类更现代的压测工具。
火焰图可视化生成器
Flame Graph工程实现了一套生成火焰图的脚本。可以将其clone下来:
git clone https://github.com/brendangregg/FlameGraph.git
一般生成和创建火焰图,需要如下几个步骤:
- 捕获堆栈。可以使用perf、systemtap、dtrace等工具抓取程序的运行堆栈。
- 折叠堆栈。Tracer工具抓取的程序每时刻的堆栈信息,需要对他们进行分析组合,将重复的堆栈累计在一起,从而体现出负载和关键路径。由FlameGraph中的stackcollapse程序执行。
- 生成火焰图。分析由stackcollapse程序输出的堆栈信息生成的火焰图。
不同的Tracer工具抓取到的信息不同,因此FalmeGraph提供了一系列的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生成火焰图
安装perf
很多Linux系统已经自带了perf工具,但是Ubuntu需要手动安装,安装命令如下:
sudo apt install linux-tools-common
sudo apt install linux-tools-generic linux-cloud-tools-generic
直接打perf命令测试一般会出现如下提示:
$ perf
WARNING: perf not found for kernel 5.4.0-77
You may need to install the following packages for this specific kernel:
linux-tools-5.4.0-77-generic
linux-cloud-tools-5.4.0-77-generic
这时只需要再安装:
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install linux-tools-5.4.0-77-generic linux-cloud-tools-5.4.0-77-generic
然后就安装OK了。
perf采集数据
perf(performance)是Linux系统原生提供的性能分析工具,会返回CPU正在执行的函数名以及调用栈。使用perf采集数据命令如下:
sudo perf record -F 99 -p 2512 -g -- sleep 60
- record:表示采集系统事件,没有采用-e执行采集事件,则默认采集cycles(即CPU clock周期)。
- -F 99:指定采样频率为99Hz(每秒99次),如果99次都返回同一个函数名, 那就说明CPU这一秒钟都在执行同一个函数,可能存在性能问题。
- -p 2512:指定进程号,对某一个进程分析。
- -g:表示记录调用栈。
- sleep 30:表示持续30秒
之后就会得到一个perf.data的文件,输出如下:
$ sudo perf record -F 99 -p 3092020 -g -- sleep 30
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.013 MB perf.data ]
$ ls
perf.data
同时为了便于阅读,perf record命令可以统计每个调用栈出现的百分比,然后从高到低排列。
$ sudo perf report -n --stdio
出现内容如下:
# To display the perf.data header info, please use --header/--header-only options.
#
# Total Lost Samples: 0
# Samples: 67 of event 'cpu-clock:pppH'
# Event count (approx.): 676767670
#
# Children Self Samples Command Shared Object Symbol >
# ........ ........ ............ ......... ................... ....................................>
#
88.06% 0.00% 0 test_perf [unknown] [k] 0000000000000000
|
---0
|
|--86.57%--__GI___libc_write
| |
| --47.76%--entry_SYSCALL_64_after_hwframe
| do_syscall_64
| |
| --44.78%--__x64_sys_write
| ksys_write
| vfs_write
| __vfs_write
| tty_write
| |
| |--40.30%--n_tty_write
| | |
| | |--17.91%--do_output_char
| | | |
| | | |--16.42%--pty_write
因为这个结果还是很难读懂,所以才有了火焰图。
生成火焰图
首先使用perf script工具对上面生成的perf.data进行解析:
# 生成折叠后的调用栈
sudo perf script -i perf.data &> perf.unfold
将解析出来的信息存下来,供生成火焰图。首先用stackcollapse-perf.pl将perf.unfold中的符号进行折叠:
# 生成火焰图
./stackcollapse-perf.pl perf.unfold &> perf.folded
最后生成svg图:
./flamegraph.pl perf.folded > perf.svg
我们可以使用管道将上面的流程简化为一条命令:
sudo perf script | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > perf.svg
解析火焰图
直接使用浏览器打开perf.svg即可,或者将其下载到本地用浏览器打开也OK。
互动性
火焰图因为是svg图片,可以与用户互动:
- 鼠标悬浮。火焰每一层都会标注函数名,鼠标悬浮时会显示完整的函数名、抽样抽中的次数、占据总抽样次数的百分比。
- 点击放大。在某一层点击,火焰图会水平放大,该层会占据所有宽度,显示详细信息。左上角会同时显示“Reset Zoom”,点击该链接,图片就会恢复原样。
- 搜索。按下Ctrl + F会显示一个搜索框,用户可以输入关键词或正则表达式,所有符合条件的函数名会高亮显示。
局限性
当出现这两种情况下,无法画出火焰图,需要修正系统行为。
- 调用栈不完整。当调用栈过深时,某些系统只返回前面的一部分(比如前10层)。
- 函数名缺失。有些函数没有名字,编译器只用内存地址来表示(比如匿名函数)。有可能是因为编译器优化等级太高。
火焰图的拓展
上面的火焰图FlameGraph我们习惯性称之为CPU火焰图。其实火焰图还有其他的种类,比如下面介绍的几种。
浏览器的火焰图
Chrome就可以生成页面脚本的火焰图,用来进行CPU分析。
开发者工具,切换到Performance面板。然后,点击“录制”按钮,开始记录数据。这时可以在页面进行各种操作,然后停止“录制”。然后,开发者工具会显示一个时间轴,它的下方就是火焰图。
浏览器的火焰图与标准火焰图有两点差异:它是倒置的(即调用栈最顶端的函数在最下方),X轴是时间轴,而不是抽样次数。
红蓝分叉火焰图
有了CPU火焰图,CPU使用率的问题一般都比较好定位。但要处理性能回退问题,就要在修改前后或者不同时期和场景下的火焰图之间,不断切换对比,来找出问题所在,这感觉就是像在太阳系中搜寻冥王星。虽然,这种方法可以解决问题,但我觉得应该会有更好的办法。
所以,就有红/蓝差分火焰图(red/blue differential flame graphs)。其形式大概如下:
红蓝交叉火焰图也是一副交互式SVG格式图片,但是用了两种不同颜色来表示,红色表示增长,蓝色表示衰减。
与CPU火焰图不同的是,在红/蓝交叉火焰图中,使用不同的颜色来表示两个profile文件中的差异部分。在第二个profile中deflate_slow( )函数以及它后续调用的函数运行的次数要比前一次更多,所以在上图中这个栈帧被标为了红色。可以看出问题的原因是ZFS的压缩功能被启用了,而在系统升级前这项功能是关闭的。
想象一下, 如果是在分析一个微小的性能下降,比如说小于5%,而且代码也更加复杂的时候,红蓝交叉火焰图就比普通CPU火焰图强大了。
但其也有不足之处:如果一个代码执行路径完全消失了,那么在火焰图中就找不到地方来标注蓝色。你只能看到当前的CPU使用情况,而不知道为什么会变成这样。一个办法是,将对比顺序颠倒,画一个相反的差分火焰图。
具体这里不做赘述,只做一个介绍,还有一些其他的差分火焰图,可以自行了解。
如果你遇到了性能回退问题,红/蓝差分火焰图是找到根因的最快方式。这种方式抓取了两张普通的火焰图,然后进行对比,并对差异部分进行标色:红色表示上升,蓝色表示下降。 差分火焰图是以当前(“修改后”)的profile文件作为基准,形状和大小都保持不变。因此你通过色彩的差异就能够很直观的找到差异部分,且可以看出为什么会有这样的差异。差分火焰图可以应用到项目的每日构建中,这样性能回退的问题就可以及时地被发现和修正。