Linux Flame Graphs

23 篇文章 1 订阅

火焰图仅用一张小图,就可以定量展示所有的性能瓶颈的全景图,而不论目标软件有多么复杂。

传统的性能分析工具通常会给用户展示大量的细节信息和数据, 而用户很难看到全貌,反而容易去优化那些并不重要的地方,经常浪费大量时间和精力却看不到明显效果。传统分析器的另一个缺点是,它们通常会孤立地显示每个函数调用的延时,但很难看出各个函数调用的上下文,而且用户还须刻意区分当前函数本身运行的时间(exclusive time)和包括了其调用其他函数的时间在内的总时间(inclusive time)。

而相比之下,火焰图可以把大量信息压缩到一个大小相对固定的图片当中(通常一屏就可以显示全)。 不怎么重要的代码路径会在图上自然地淡化乃至消失,而真正重要的代码路径则会自然地凸显出来。越重要的,则会显示得越明显。火焰图总是为用户提供最适当的信息量,不多,也不少。

官方博客:https://www.brendangregg.com/flamegraphs.html

火焰图能做什么:

  • 可以分析函数执行的频繁程度

  • 可以分析哪些函数经常阻塞

  • 可以分析哪些函数频繁分配内存

  • 以分析程序的性能瓶颈。

火焰图整个图形看起来就像一个跳动的火焰,这就是它名字的由来。

火焰图有以下特征(这里以 on-cpu 火焰图为例):

  • 每一列代表一个调用栈,每一个格子代表一个函数

  • 纵轴展示了栈的深度,按照调用关系从下到上排列。最顶上格子代表采样时,正在占用 cpu 的函数。

  • 横轴的意义是指:火焰图将采集的多个调用栈信息,通过按字母横向排序的方式将众多信息聚合在一起。需要注意的是它并不代表时间。

  • 横轴格子的宽度代表其在采样中出现频率,所以一个格子的宽度越大,说明它是瓶颈原因的可能性就越大。

  • 火焰图格子的颜色是随机的暖色调,方便区分各个调用信息。

  • 其他的采样方式也可以使用火焰图, on-cpu 火焰图横轴是指 cpu 占用时间,off-cpu 火焰图横轴则代表阻塞时间。

  • 采样可以是单线程、多线程、多进程甚至是多 host,进阶用法可以参考附录进阶阅读。

火焰图类型

常见的火焰图类型有 On-CPU,Off-CPU,还有 Memory,Hot/Cold,Differential 等等。它们有各自适合处理的场景。

什么时候使用 On-CPU 火焰图? 什么时候使用 Off-CPU 火焰图呢?

取决于当前的瓶颈到底是什么:

  • 如果是 CPU 则使用 On-CPU 火焰图,

  • 如果是 IO 或锁则使用 Off-CPU 火焰图.

  • 如果无法确定, 那么可以通过压测工具来确认:

  • 通过压测工具看看能否让 CPU 使用率趋于饱和, 如果能那么使用 On-CPU 火焰图

  • 如果不管怎么压, CPU 使用率始终上不来, 那么多半说明程序被 IO 或锁卡住了, 此时适合使用 Off-CPU 火焰图.

  • 如果还是确认不了, 那么不妨 On-CPU 火焰图和 Off-CPU 火焰图都搞搞, 正常情况下它们的差异会比较大, 如果两张火焰图长得差不多, 那么通常认为 CPU 被其它进程抢占了.

火焰图分析技巧

  • 纵轴代表调用栈的深度(栈桢数),用于表示函数间调用关系:下面的函数是上面函数的父函数。

  • 横轴代表调用频次,一个格子的宽度越大,越说明其可能是瓶颈原因。

  • 不同类型火焰图适合优化的场景不同,比如 on-cpu 火焰图适合分析 cpu 占用高的问题函数,off-cpu 火焰图适合解决阻塞和锁抢占问题。

  • 无意义的事情:横向先后顺序是为了聚合,跟函数间依赖或调用关系无关;火焰图各种颜色是为方便区分,本身不具有特殊含义

  • 多练习:进行性能优化有意识的使用火焰图的方式进行性能调优(如果时间充裕)

如何绘制火焰图?

生成火焰图的流程

Brendan D. Gregg 的 Flame Graph 工程实现了一套生成火焰图的脚本。Flame Graph 项目位于 GitHub上

https://github.com/brendangregg/FlameGraph

当GitHub网络不通畅的时候可以使用码云的链接:

git clone https://gitee.com/mirrors/FlameGraph.git

用 git 将其 clone下来

生成和创建火焰图需要如下几个步骤

不同的 trace 工具抓取到的信息不同, 因此 Flame Graph 提供了一系列的 stackcollapse 工具.

安装perf

apt install linux-tools-common

测试perf是否可用

perf record -F 99 -a -g -- sleep 10    

如果报错

WARNING: perf not found for kernel 4.15.0-48

You may need to install the following packages for this specific kernel:
linux-tools-4.15.0-48-generic
linux-cloud-tools-4.15.0-48-generic

You may also want to install one of the following packages to keep up to date:
linux-tools-generic
linux-cloud-tools-generic

apt install linux-tools-generic
apt install linux-cloud-tools-generic

则需要安装linux-tools-generic和linux-cloud-tools-generic,但需选择对应的版本,比如提示的是4.15.0-48,则我们安装4.15.0-48版本

# apt-get install linux-tools-4.15.0-48-generic linux-cloud-tools-4.15.0-48-generic linux-tools-generic linux-cloud-tools-generic

再次测试

perf record -F 99 -a -g -- sleep 10    
  • record:表示采集系统事件,没有采用 -e 执行采集事件,则默认采集 cycles(即 CPU clock 周期)。

  • -F 99:指定采样频率为 99Hz(每秒99次),如果 99次都返回同一个函数名, 那就说明 CPU 这一秒钟都在执行同一个函数,可能存在性能问题。

  • -p 2512:指定进程号,对某一个进程分析。

  • -g:表示记录调用栈。

  • sleep 30:表示持续 30 秒

之后就会得到一个 perf.data 的文件

perf常用命令

查看帮助文档,perf功能非常强大,我们这里只关注record和report功能,record和report也可以继续通过二级命令查询帮助文档。

perf -h

常用的五个命令:

  • perf list:查看当前软硬件环境支持的性能事件

  • perf stat:分析指定程序的性能概况

  • perf top:实时显示系统/进程的性能统计信息

  • perf record:记录一段时间内系统/进程的性能事件perf report:读取perf record生成的perf.data文件,并显示分析数据(生成火焰图用的采集命令)

perf record -h

常用命令

  • -e <event>:指定性能事件(可以是多个,用,分隔列表)

  • -p <pid>:指定待分析进程的 pid(可以是多个,用,分隔列表)

  • -t <tid>:指定待分析线程的 tid(可以是多个,用,分隔列表)

  • -u <uid>:指定收集的用户数据,uid为名称或数字

  • -a:从所有 CPU 收集系统数据

  • -g:开启 call-graph (stack chain/backtrace) 记录

  • -C <cpu-list>:只统计指定 CPU 列表的数据,如:0,1,3或1-2

  • -r <RT priority>:perf 程序以SCHED_FIFO实时优先级RT priority运行这里填入的数值越大,进程优先级越高(即 nice 值越小)

  • -c <count>: 事件每发生 count 次采一次样

  • -F <n>:每秒采样 n 次

  • -o <output.data>:指定输出文件output.data,默认输出到perf.data

perf 采集数据

perf record -F 99 -p 3887 -g -- sleep 30

perf record 表示采集系统事件, 没有使用 -e 指定采集事件, 则默认采集 cycles(即 CPU clock 周期), -F 99 表示每秒 99 次, -p 13204 是进程号, 即对哪个进程进行分析, -g 表示记录调用栈, sleep 30 则是持续 30 秒.

-F 指定采样频率为 99Hz(每秒99次), 如果 99次 都返回同一个函数名, 那就说明 CPU 这一秒钟都在执行同一个函数, 可能存在性能问题.

运行后会产生一个庞大的文本文件. 如果一台服务器有 16 个 CPU, 每秒抽样 99 次, 持续 30 秒, 就得到 47,520 个调用栈, 长达几十万甚至上百万行.

为了便于阅读, perf record 命令可以统计每个调用栈出现的百分比, 然后从高到低排列.

perf report -n --stdio

生成火焰图

首先用 perf script 工具对 perf.data 进行解析

# 生成折叠后的调用栈
perf script -i perf.data &> perf.unfold

然后将解析出来的信息存下来, 供生成火焰图

stackcollapse-perf.pl 将 perf 解析出的内容 perf.unfold 中的符号进行折叠 :

# 生成火焰图
./FlameGraph/stackcollapse-perf.pl perf.unfold &> perf.folded

最后生成 svg 图

# ./FlameGraph/flamegraph.pl
perf.folded > perf.svg

我们可以使用管道将上面的流程简化为一条命令

perf script | ./FlameGraph/stackcollapse-perf.pl | ./FlameGraph/flamegraph.pl > process.svg

解析火焰图

最后就可以用浏览器打开火焰图进行分析啦.

火焰图的含义

火焰图是基于 stack 信息生成的 SVG 图片, 用来展示 CPU 的调用栈。

  • y 轴表示调用栈, 每一层都是一个函数. 调用栈越深, 火焰就越高, 顶部就是正在执行的函数, 下方都是它的父函数.

  • x 轴表示抽样数, 如果一个函数在 x 轴占据的宽度越宽, 就表示它被抽到的次数多, 即执行的时间长. 注意, x 轴不代表时间, 而是所有的调用栈合并后, 按字母顺序排列的.

  • 火焰图就是看顶层的哪个函数占据的宽度最大. 只要有 “平顶”(plateaus), 就表示该函数可能存在性能问题。

  • 颜色没有特殊含义, 因为火焰图表示的是 CPU 的繁忙程度, 所以一般选择暖色调.

互动性

火焰图是 SVG 图片,可以与用户互动。

(1)鼠标悬浮

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

mysqld'JOIN::exec (272,959 samples, 78.34 percent)

off-cpu是否也是如此?

(2)点击放大 在某一层点击,火焰图会水平放大,该层会占据所有宽度,显示详细信息。

(3)搜索

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

on-cpu火焰图

使用perf采集

perf record -F 99 -p 34631 -g -- sleep 120

生成火焰图

perf script | ./FlameGraph/stackcollapse-perf.pl | ./FlameGraph/flamegraph.pl > zeromq-req-rep.svg

off-cpu火焰图

使能sched_schedstats统计

需要在root权限下使能

echo 1 > /proc/sys/kernel/sched_schedstats

使用perf采集

-p 是对应要采集程序的进程ID

perf record -e sched:sched_stat_sleep -e sched:sched_switch -e sched:sched_process_exit -p 43777 -g -o perf.data.raw sleep 10 # perf inject -v -s -i perf.data.raw -o perf.data

通过perf分别记录sched:sched_stat_sleep、sched:sched_switch、sched:sched_process_exit三种事件,这三种事件分别表示:进程主动放弃 CPU 而进入睡眠的等待事件、进程由于I/O和锁等待等原因被调度器切换而进入睡眠的等待事件、进程的退出事件。

生成火焰图

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 > offcpu.svg

perf基本使用

它和Oprofile性能调优工具等的基本原理都是对被监测对象进行采样,最简单的情形是根据 tick 中断进行采样,即在 tick 中断内触发采样点,在采样点里判断程序当时的上下文。假如一个程序 90% 的时间都花费在函数 foo() 上,那么 90% 的采样点都应该落在函数 foo() 的上下文中。运气不可捉摸,那么只要采样频率足够高,采样时间足够长,那么以上推论就比较可靠。因此,通过 tick 触发采样,我们便可以了解程序中哪些地方最耗时间,从而重点分析。

上面介绍了perf的原理,“根据 tick 中断进行采样,即在 tick 中断内触发采样点,在采样点里判断程序当时的上下文”,我们可以改变采样的触发条件使得我们可以获得不同的统计数据,例如 以时间点 ( 如 tick) 作为事件触发采样便可以获知程序运行时间的分布;以 cache miss 事件触发采样便可以知道 cache miss 的分布,即 cache 失效经常发生在哪些程序代码中。如此等等。

首先我们可以看一下 perf 中能够触发采样的事件有哪些。

perf list使用,可以列出所有的采样事件

sudo perf list

可以看到 Hadrware event Software event等

(1)Hardware Event 是由 PMU 硬件产生的事件,比如 cache 命中,当您需要了解程序对硬件特性的使用情况时,便需要对这些事件进行采样

(2)Software Event 是内核软件产生的事件,比如进程切换,tick 数等

(3)Tracepoint event 是内核中的静态 tracepoint 所触发的事件,这些 tracepoint 用来判断程序运行期间内核的行为细节,比如 slab 分配器的分配次数等

perf stat 概览程序的运行情况

perf stat选项,可以在终端上执行命令时收集性能统计信息

sudo perf stat -p 11664

指定进程查看,ctrl +c 杀死进程之后,就可以看到相应的数据了。

(1)task-clock(msec)是指程序运行期间占用了xx的任务时钟周期,该值高,说明程序的多数时间花费在 CPU 计算上而非 IO

(2)context-switches是指程序运行期间发生了xx次上下文切换,记录了程序运行过程中发生了多少次进程切换,频繁的进程切换是应该避免的。(有进程进程间频繁切换,或者内核态与用户态频繁切换)

(3)cpu-migrations 是指程序运行期间发生了xx次CPU迁移,即用户程序原本在一个CPU上运行,后来迁移到另一个CPU

(4)cycles:处理器时钟,一条机器指令可能需要多个 cycles

(5)Instructions: 机器指令数目。

其他可以监控的譬如分支预测、cache命中,page-faults 是指程序发生了xx次页错误等

perf top实时显示当前系统的性能统计信息

sudo perf top -g

用于实时显示当前系统的性能统计信息。该命令主要用来观察整个系统当前的状态,比如可以通过查看该命令的输出来查看当前系统最耗时的内核函数或某个用户进程。

[.] : user level 用户态空间,若自己监控的进程为用户态进程,那么这些即主要为用户态的cpu-clock占用的数值

[k]: kernel level 内核态空间

[g]: guest kernel level (virtualization) 客户内核级别

[u]: guest os user space 操作系统用户空间

[H]: hypervisor 管理程序

The final column shows the symbol name.

当 perf 收集调用链时,开销可以在两列中显示为Children和Self 。这里的Self列与没有“-g”的列类似:这是每个函数花费的 CPU 周期百分比。但是Children列在其下方添加了所有调用函数所花费的时间。不仅是直系子女,而且是所有后代。对于调用图的叶子,函数不调用其他任何东西,Self 和 Children 的值是相等的。但是对于 main(),它增加了在 f1()<-main() 和 f2()<-main() 中花费的时间。您将第一行读为: 95.61% 的时间花在调用 main() 上,而只有 8.19% 的时间花在 main() 指令上,因为它大部分时间都在调用其他函数。请注意,您可以添加“Self”以覆盖 100%,但在“Children”中,儿童样本占多行。这个想法是在顶部查看占样本最多的调用堆栈片段。

有一个“+”,可以向下查看调用关系。

perf record 记录采集的数据

使用 top 和 stat 之后,perf可能已经大致有数了。要进一步分析,便需要一些粒度更细的信息。比如说我们已经断

使用 top 和 stat 之后,perf可能已经大致有数了。要进一步分析,便需要一些粒度更细的信息。比如说我们已经断定目标程序计算量较大,也许是因为有些代码写的不够精简。那么面对长长的代码文件,究竟哪几行代码需要进一步修改呢?这便需要使用 perf record 记录单个函数级别的统计信息,并使用 perf report 来显示统计结果(perf record表示记录到文件,perf top直接会显示到界面)。

perf record ,它可以对事件进行采样,将采样的数据收集在一个 perf.data 的文件中,这将会带来一定的性能开销,不过这个命令很有用,可以用来找出最占 CPU 的进程。

下面的命令对系统 CPU 事件做采样,采样时间为 60 秒,每秒采样 99 个事件,-g表示记录程序的调用栈。

sudoperf record -F 99 -a -g -- sleep 60

此外我们还可以使用PID监控程序perf record -e cpu-clock -g -p pid 监控 已启动的进程;也可以使用程序名监控程序perf record -e cpu-clock -g -p grep your_program

-e选项允许您在perf list命令中列出的多个类别中选择一个事件类别。例如,在这里,我们使用-e cpu-clock 是指perf record监控的指标为cpu周期程序运行完之后,perf record会生成一个名为perf.data的文件(缺省值),如果之前已有,那么之前的perf.data文件会变为perf.data.old文件

-g选项是告诉perf record额外记录函数的调用关系,因为原本perf record记录大都是库函数,直接看库函数,大多数情况下,你的代码肯定没有标准库的性能好对吧?除非是针对产品进行特定优化,所以就需要知道是哪些函数频繁调用这些库函数,通过减少不必要的调用次数来提升性能

perf record的其他参数:

-f:强制覆盖产生的.data数据

-c:事件每发生count次采样一次

-p:指定进程

-t:指定线程

可以使用ctrl+c中断perf进程,或者在命令最后加上参数 --sleep n (n秒后停止)

perf report

sudo perf report -n可以生成报告的预览。

sudo perf report -n --stdio可以生成一个详细的报告。

sudo perf script可以 dump 出 perf.data 的内容。

获得这个perf.data文件之后,我们其实还不能直接查看,下面就需要perf report工具进行查看

perf report输出 record的结果

如果record之后想直接输出结果,使用perf report即可

perf report的相关参数:

-i : 指定文件输出

  -k:指定未经压缩的内核镜像文件,从而获得内核相关信息

  --report:cpu按照cpu列出负载

sudo perf report

如果svg图出现unknown函数,使用如下命令

sudo perf record -e cpu-clock --call-graph dwarf -p pid

perf record回溯方式

perf record 同时支持 3 种栈回溯方式:fp, dwarf, lbr,可以通过 --call-graph 参数指定,而 -g 就相当于 --call-graph fp.

(1)fp 就是 Frame Pointer,即 x86 中的 EBP 寄存器,fp 指向当前栈帧栈底地址,此地址保存着上一栈帧的 EBP 值,,根据 fp 就可以逐级回溯调用栈。然而这一特性是会被优化掉的,而且这还是 GCC 的默认行为,在不手动指定 -fno-omit-frame-pointer 时默认都会进行此优化,此时 EBP 被当作一般的通用寄存器使用,以此为依据进行栈回溯显然是错误的。不过尝试指定 -fno-omit-frame-pointer 后依然没法获取到正确的调用栈,根据 GCC 手册的说明,指定了此选项后也并不保证所有函数调用都会使用 fp…… 看来只有放弃使用 fp 进行回溯了。

(2)dwarf 是一种调试文件格式,GCC 编译时附加的 -g 参数生成的就是 dwarf 格式的调试信息,其中包括了栈回溯所需的全部信息,使用 libunwind 即可展开这些信息。dwarf 的进一步介绍可参考 “关于DWARF”,值得一提的是,GDB 进行栈回溯时使用的正是 dwarf 调试信息。实际测试表明使用 dwarf 可以很好的获取到准确的调用栈。

(3)lbr 即 Last Branch Records,是较新的 Intel CPU 中提供的一组硬件寄存器,其作用是记录之前若干次分支跳转的地址,主要目的就是用来支持 perf 这类性能分析工具,其详细说明可参考 “An introduction to last branch records” & “Advanced usage of last branch records”。此方法是性能与准确性最高的手段,然而它存在一个很大的局限性,由于硬件 Ring Buffer 寄存器的大小是有限的,lbr 能记录的栈深度也是有限的,具体值取决于特定 CPU 实现,一般就是 32 层,若超过此限制会得到错误的调用栈。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值