熟悉我的朋友可能知道,我是打中间件比赛出身,硕士一直在做分布式的东西,毕业后去了行业内比较大的厂做相对底层的东西,分布式数据库研发与性能优化。在追求极致性能的场景下,了解你的程序运行过程中cpu在干什么很重要,火焰图就是一种非常直观的展示cpu在程序整个生命周期过程中时间分配的工具。
关于火焰图介绍的文章其实比较多了,本文意在将这有点复杂的东西用最容易上手的方式介绍给大家。本文的所有代码都在Centos7.6的系统上执行过,如果有朋友发现在你的机器上执行不了也辛苦一定给我留言,这篇文章的方法也是我将来要反复要用的,任何错误和纰漏都是在坑我自己。
火焰图是什么:
#include <stdio.h>
void foo(){
int i,j;
for(i=0;i<1000;i++)
j+=2;
}
int main(void) {
int i;
for(i=0;i<100000000;i++){
foo();
}
return 0;
}
以上是一段很简单的c语言程序和与其对应的火焰图(程序用的别人的,图是我自己生成的,侵删),可以认为火焰图横轴代表程序运行的时间,上方重叠长条的就是调用栈。火焰图的颜色没有任何意义,主要要看的是某一层调用栈的时间占比。
比如对于图中的例子我们想看foo函数是否存在性能问题,我们就要首先去思考foo函数的这个绿色长条相对比在a.out这个进程的棕色长条的比例,这个图中的比例大概在95%以上,如果觉得这个比例比我们预期偏大,那么我们就可以针对性的去优化foo这个函数,看看为什么占用这么高比例的cpu时间,有没有提高这个函数的性能。
绘制火焰图的一般过程是先对程序进行profiling,你可以理解以高频率不断去打印程序的调用堆栈,然后对调用栈文件进行可视化。下面不废话,分C和Java介绍如何操作。
C/C++语言火焰图绘制
对于C/C++语言来说最常用的profiling工具是perf和systemtap。本着最易上手的原则,这里使用perf,安装简单,有些系统直接会自带。可视化工具本文选择FlameGraph,没办法,brendangregg大神实在太厉害,想要深入掌握各种profiling、性能分析技巧,这位大神以后还会经常打交道。
直接上代码,CentOS7.6系统:
#准备工作:安装git, perf,并clone可视化工具
yum install git
yum install perf
git clone https://github.com/brendangregg/FlameGraph.git
#修改系统参数
echo 1 > /proc/sys/kernel/perf_event_paranoid
echo 0 > /proc/sys/kernel/kptr_restrict
我当时以root用户运行,所以没有加sudo,实际执行的时候可能需要超级用户权限。修改的两个系统参数主要是开放perf权限,如果你是在root用户下执行,可以不用改;如果想以普通用户运行perf就一定要更改,我这里只是给出一个可用的例子,这两个参数的其他取值高级用户们可以自行探索。
# 开始绘制火焰图
perf record -p 14975 -g -- sleep 30
perf script | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > process.svg
首先说下执行这两条perf条件的必要条件:1)必须有已经运行的C进程;2)安装了FlameGraph工具,且这个文件夹在当前目录下。
第一条命令中的14975在运行时替换为你的进程号(pid), 30代表你监测性能的持续时间,单位是s,可以视情况更改。执行这条命令后会持续监测pid为14975的进程30s,然后在当前目录生成perf.data的检测文件。perf命令还有很多用法,高级用户可以-h自行探索。这里给出一些常用的,默认每秒99次profiling,如果想要更精确的结果可以用-F参数指定更高的频率,如果想检测所有进程的情况,可以去掉-p $pid参数,添加-a参数。
注意指定-p $pid的时候,这个进程号对应的进程必须是真实的,不然运行这个命令会报错,而不是生成空文件。
第二条命令就是生成火焰图结果为process.svg,这种矢量图文件直接拖到浏览器里就可以打开查看,点击某个长条的时候会放大,点击左上角的reset zoom会恢复到原来大小。
Java火焰图绘制
Java(包括其他语言的进程),你直接用profiling C的那套程序去做也能绘出图来,但大概率是满屏的Unknown,perf无法理解JVM那套符号链接,所以没法打印出有意义的堆栈,这里给出一套对Java有效的火焰图生成方法。
推荐一个开源项目,里面是各种用于java做profiling的工具,https://github.com/jvm-profiling-tools 我们选用里面的async-profiler工具,这个是因为有段时间在用java11,async-profiler相比于其他几个工具对java11支持的更好一些;
准备工作:
1、安装jdk,配置JAVA_HOME,每个人有自己的java路径,这里不给出统一代码;
2、执行以下代码安装必要工具:
# 安装perf git和async-profiler和可视化工具
yum install perf
yum install git
git clone https://github.com/jvm-profiling-tools/async-profiler.git
git clone https://github.com/brendangregg/FlameGraph.git
#修改内核参数
echo 1 > /proc/sys/kernel/perf_event_paranoid
echo 0 > /proc/sys/kernel/kptr_restrict
与C的流程一致,如果你以root运行可以不修改内核参数,如果以其他用户生成火焰图,需要修改内核参数
对指定进程进行profiling:
cd async-profiler
./profiler.sh -d 20 -o collapsed -f /home/gaia/trace.txt 236577
其中20是采集时间,单位是秒, collapsed是采集规范,-f后面的是生成的采集文件的路径,23577对应要profiling的进程号。
生成火焰图:
FlameGraph/flamegraph.pl --colors=java /home/gaia/trace.txt > /home/gaia/test.svg
/home/gaia/trace.txt代表第一步生成的数据采集文件的地址,/home/gaia/test.svg代表生成的矢量图地址,直接通过浏览器打开svg文件就可以看到火焰图。