火焰图概念
火焰图(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
- 1
一般生成和创建火焰图,需要如下几个步骤:
-
捕获堆栈。可以使用 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
- 1
- 2
- 3
然后就安装 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
- 1
出现内容如下:
# 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
- 1
解析火焰图
直接使用浏览器打开 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 文件作为基准,形状和大小都保持不变。因此你通过色彩的差异就能够很直观的找到差异部分,且可以看出为什么会有这样的差异。差分火焰图可以应用到项目的每日构建中,这样性能回退的问题就可以及时地被发现和修正。
参考
- https://www.brendangregg.com/flamegraphs.html
- https://www.ruilog.com/notebook/view/9ff8def96d9c.html
- https://wenfh2020.com/2020/07/30/flame-diagram/
- http://www.ruanyifeng.com/blog/2017/09/flame-graph.html
</article>