使用火焰图(FlameGraph)分析程序性能

火焰图概念

火焰图(FlameGraph)是 svg 格式的矢量图,是先通过 perf 工具分析得到的结果,并将其生成的具有不同层次且支持互动的图片,看起来就像是火焰,这也正是它的名字的由来。其表现形式大概如下所示:

使用火焰图(FlameGraph)分析程序性能

需要有以下几点注意:

  1. 纵向(Y 轴)高低不平,表示的是函数调用栈的深度。每一层都是一个函数。调用栈越深,火焰就越高,顶部就是正在执行的函数,下方都是它的父函数。

  2. 横向(X 轴)表示该函数执行消耗的时间,横向上会按照字母顺序排序,而且如果是同样的调用会做合并(注意:如果一个函数在 X 轴占据的宽度越宽,就表示它被抽到的次数多,即执行的时间长,所以这里不是严格意义上的执行消耗的事件),所以一个横向宽度越大的函数调用,一般很可能是程序的瓶颈。

  3. 火焰图的颜色是随机分配的,并不是颜色越深就是越瓶颈。因为火焰图表示的是 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 等工具抓取程序的运行堆栈。

  2. 折叠堆栈。Tracer 工具抓取的程序每时刻的堆栈信息,需要对他们进行分析组合,将重复的堆栈累计在一起,从而体现出负载和关键路径。由 FlameGraph 中的 stackcollapse 程序执行。

  3. 生成火焰图。分析由 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 
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

然后再测试就已经成功安装。

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 图片,可以与用户互动:

  1. 鼠标悬浮。火焰每一层都会标注函数名,鼠标悬浮时会显示完整的函数名、抽样抽中的次数、占据总抽样次数的百分比。

  2. 点击放大。在某一层点击,火焰图会水平放大,该层会占据所有宽度,显示详细信息。左上角会同时显示“Reset Zoom”,点击该链接,图片就会恢复原样。

  3. 搜索。按下 Ctrl + F 会显示一个搜索框,用户可以输入关键词或正则表达式,所有符合条件的函数名会高亮显示。

局限性

当出现这两种情况下,无法画出火焰图,需要修正系统行为。

  1. 调用栈不完整。当调用栈过深时,某些系统只返回前面的一部分(比如前10层)。
  2. 函数名缺失。有些函数没有名字,编译器只用内存地址来表示(比如匿名函数)。有可能是因为编译器优化等级太高。

火焰图的拓展

上面的火焰图 FlameGraph 我们习惯性称之为 CPU 火焰图。其实火焰图还有其他的种类,比如下面介绍的几种。

浏览器的火焰图

Chrome 就可以生成页面脚本的火焰图,用来进行 CPU 分析。

开发者工具,切换到 Performance 面板。然后,点击“录制”按钮,开始记录数据。这时可以在页面进行各种操作,然后停止“录制”。然后,开发者工具会显示一个时间轴,它的下方就是火焰图。

使用火焰图(FlameGraph)分析程序性能

浏览器的火焰图与标准火焰图有两点差异:它是倒置的(即调用栈最顶端的函数在最下方),X 轴是时间轴,而不是抽样次数。

红蓝分叉火焰图

有了 CPU 火焰图,CPU 使用率的问题一般都比较好定位。但要处理性能回退问题,就要在修改前后或者不同时期和场景下的火焰图之间,不断切换对比,来找出问题所在,这感觉就是像在太阳系中搜寻冥王星。虽然,这种方法可以解决问题,但我觉得应该会有更好的办法。

所以,就有红/蓝差分火焰图(red/blue differential flame graphs)。其形式大概如下:

使用火焰图(FlameGraph)分析程序性能

红蓝交叉火焰图也是一副交互式 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
上一篇:51_串口


下一篇:火焰图基本使用教程