01 前言
软件同学在进行性能分析时,通常需要查看 CPU 耗时,用于了解性能瓶颈在哪里,从而进行针对性的优化。火焰图(Flame Graph)是常用的性能分析工具。总结来看,火焰图通常用于以下场景:
- 代码性能调优:发现和优化代码的性能瓶颈。
- 资源使用监控:在生产环境中监控应用程序的资源使用情况。
- 故障排查:定位异常情况,例如 CPU 使用率突增的原因。
火焰图能够显示代码的调用堆栈、各个函数的执行频率以及它们之间的调用关系。整个图形看起来就像一个跳动的火焰,这就是它名字的由来。
火焰图有以下特征:
- 纵轴:表示函数调用堆栈的深度,每一列代表一个调用栈,每一个格子代表一个函数。最顶上格子代表采样时,正在占用 cpu 的函数。纵轴也展示了栈的深度,按照调用关系从下到上排列。
- 横轴:火焰图将采集的多个调用栈信息,通过按字母横向排序的方式将众多信息聚合在一起。如果一个函数在 x 轴占据的宽度越宽,就表示它被抽到的次数多,即执行的时间长。需要注意:横轴不代表时间。
- 颜色:颜色是随机的暖色调,方便区分不同的线程、代码模块或特定函数类型,没有其他特殊含义。
- 瓶颈:顶层的哪个函数占据的宽度最大,通常瓶颈就发生在这个"平顶"。
02 火焰图理解示例
本节参考:https://www.ruanyifeng.com/blog/2017/09/flame-graph.html
下面是一个用于帮助理解火焰图怎么形成的小例子。
首先,CPU 抽样得到了三个调用栈。
func_c
func_b
func_a
start_thread
func_d
func_a
start_thread
func_d
func_a
start_thread
上面代码中,start_thread 是启动线程,调用了 func_a。后者又调用了 func_b 和 func_d,而 func_b 又调用了 func_c。经过合并处理后,得到了下面的结果,即存在两个调用栈,第一个调用栈抽中 1 次,第二个抽中 2 次。
start_thread;func_a;func_b;func_c 1
start_thread;func_a;func_d 2
有了这个调用栈统计,火焰图工具就能生成 SVG 图片。
上面图片中,最顶层的函数 g()占用 CPU 时间最多。d()的宽度最大,但是它直接耗用 CPU 的部分很少。b()和 c()没有直接消耗 CPU。因此,如果要调查性能问题,首先应该调查 g(),其次是 i()。
另外,从图中可知 a()有两个分支 b()和 h(),这表明 a()里面可能有一个条件语句,而 b()分支消耗的 CPU 大大高于 h()。
不知道有没有这样的疑问:为什么上数第二层 e()+f()的总宽度小于下层 d()的总宽度?
因为不能有无父之子,下一层父宽度必大于等于上一层子宽度。
整个火焰图是纵向 n 条调用栈、然后做横向合并而来,合并得到的宽度代表了调用栈中的栈片段重复情况。调用栈是一个非常强调父子关系的数据结构,如:
a->b->c 的调用,在这“一个”调用栈上,不可能[父 b]被调用了 3 次而[子 c]被调用了 4 次。
但[子 c]可以调用 3 次,[父 b]被调用了 4 次:这代表了 a->b->c 的调用了出现了 3 次,a->b 出现了 1 次。
03 火焰图如何查看
生成火焰图通常需要两步:
- 收集性能数据:使用性能分析工具(如 perf、BPF、dtrace 等)收集 CPU 采样数据,捕获每个函数的调用堆栈和消耗时间。
- 生成火焰图:通过工具将采集的数据转换为火焰图。例如,Flamegraph.pl 是一个 Perl 脚本,可以解析 perf 的输出数据并生成 SVG 格式的火焰图。用浏览器打开 svg 格式的图片,否则无法用鼠标点击查看每个方块内详细信息;
在线火焰图示例:https://queue.acm.org/downloads/2016/Gregg4.svg
(1)鼠标悬浮
火焰的每一层都会标注函数名,鼠标悬浮时会显示完整的函数名、抽样抽中的次数、占据总抽样次数的百分比。
(2)点击放大
在某一层点击,火焰图会水平放大,该层会占据所有宽度,显示详细信息。左上角会同时显示"Reset Zoom",点击该链接,图片就会恢复原样。
(3)搜索
Ctrl + F 会显示一个搜索框,可以输入关键词或正则表达式,所有符合条件的函数名会高亮显示。
04 征程 6 火焰图实例
4.1 检查环境
检查系统内核是否开启 CONFIG_PERF_EVENTS,在 征程 5 上可以运行以下指令进行检测
sysctl -a |grep -i "perf"
开启状态下会有以下结果,如果是关闭状态则需要编译内核镜像开启 perf。
kernel.perf_cpu_time_max_percent = 25
kernel.perf_event_max_contexts_per_stack = 8
kernel.perf_event_max_sample_rate = 100000
kernel.perf_event_max_stack = 127
kernel.perf_event_mlock_kb = 576
kernel.perf_event_paranoid = 2
kernel.perf_user_access = 0
4.2 工具使用
采样:
# -p pid 进程号,找到需要分析的进程id
# -- sleep 采样时长 s, perf.data 一般较大,运行需要放在空间较大的目录,不要运行过久
# -a 所有 cpu 信息
# -g 得到函数调用关系
perf record -p 11229 -ag -- sleep 10
处理采样结果,生成火焰图所需的 perf.unfold 文件:
perf script -i perf.data > perf.unfold
绘图:
获取绘图工具:https://github.com/brendangregg/FlameGraph.git,下载到开发机上,文件夹中会有对应的工具
运行如下两行命令,最后生成的 svg 文件即为火焰图.
./FlameGraph/stackcollapse-perf.pl ./perf.unfold > perf.folded
./FlameGraph/flamegraph.pl perf.folded > perf.svg
如果想更深入了解火焰图,可以参考:http://www.brendangregg.com/perf.html#FlameGraphs
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。