使用火焰图做性能分析

 2018-01-13 16:49:02     火焰图  性能分析  Java  Flame Graph   637


导读: 火焰图(Flame Graph)是由 `Linux` 性能优化大师 `Brendan Gregg` 发明的。它是定位疑难杂症的神器,比如 `CPU` 占用高、内存泄漏等问题。

和所有其他的 `trace` 和 `profiling` 方法不同的是,`Flame Graph` 以一个全局的视野来看待时间分布,它从底部往顶部,列出所有可能的调用栈。

Flame Graph


火焰图看起来就像一团跳动的火焰,这也正是其名字的由来。燃烧在火苗尖部的就是 CPU 正在执行的操作,不过需要说明的是颜色是随机的,本身并没有特殊的含义。

纵向表示调用栈的深度(或 code-path),横向表示消耗的时间。因为调用栈在横向会按照字母排序,并且同样的调用栈会做合并,所以,一个格子的宽度越大越说明其可能是瓶颈。

综上所述,主要就是看那些比较宽大的火苗,特别留意那些类似平顶山的火苗。

详细介绍可以看阮一峰的如何读懂火焰图

# 安装
git clone https://github.com/brendangregg/FlameGraph

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

流程 描述 脚本
捕获堆栈 使用 perf/systemtap/dtrace 等工具抓取程序的运行堆栈 perf/systemtap/dtrace
折叠堆栈 trace 工具抓取的系统和程序运行每一时刻的堆栈信息, 需要对他们进行分析组合, 将重复的堆栈累计在一起, 从而体现出负载和关键路径 FlameGraph 中的 stackcollapse 程序
生成火焰图 分析 stackcollapse 输出的堆栈信息生成火焰图 flamegraph.pl

这里的 捕获堆栈,用的是 systemtap。如果是 JavaLua 等应用程序,还需要借助第三方 perf 工具来完成堆栈数据的采集。这里,Java 用的是lightweight-java-profilerNginx 用的是openresty-systemtap-toolkit

Flame Graph 提供了一系列的 stackcollapse

stackcollapse 描述
stackcollapse.pl 用于 DTrace 堆栈
stackcollapse-perf.pl 用于 Linux perf_events perf 脚本输出
stackcollapse-pmc.pl 用于 FreeBSD pmcstat -G 堆栈
stackcollapse-stap.pl 用于 SystemTap 堆栈
stackcollapse-instruments.pl 用于 XCode 工具
stackcollapse-vtune.pl 用于英特尔 VTune 捕获
stackcollapse-ljp.awk 用于Lightweight Java 捕获
stackcollapse-jstack.pl 用于Java jstack(1) 输出
stackcollapse-gdb.pl 用于 gdb(1) 堆栈
stackcollapse-go.pl 用于 Golang pprof 堆栈
stackcollapse-vsprof.pl 用于 Microsoft Visual Studio 捕获

SystemTap


SystemTap 是对 Linux 内核监控和跟踪的工具。由于 SystemTap 运行需要内核的调试信息支撑,默认发行版的内核在配置时这些调试开关没有打开,所以,安装完 SystemTap 也是无法去探测内核信息的。需要先去 debuginfo 网站(CentOS debuginfoUbuntu 内核资源)下载对应的内核包。

因为 SystemTap 对内核的检查机制非常严格,如果只是使用 uname -r 查看系统内核,会省略内核的小版本号。而小版本号不对应的话,则会导致无法使用。

Ubuntu 为例:

> uname -r #此方式无法查看小版本号
4.4.0-85-generic

> cat /proc/version_signature  #此方式可以看到小版本号, 108 就是小版本号

Ubuntu 4.4.0-85.108-generic 4.4.73

Ubuntu安装SystemTap

再次强调,一定要选对系统版本号,否则后续无法正常执行。内核资源地址。下载可能会比较耗时,需要耐心等待。

如果服务器可以重启的话,最好先更新一下内核,然后再重启一次。

apt-get update
apt-get upgrade

下载并安装 debuginfo

#以下为我的系统内核版本地址,并不一定适用,请查找对应的版本下载
wget http://ddebs.ubuntu.com/pool/main/l/linux/linux-image-4.4.0-85-generic-dbgsym_4.4.0-85.108_amd64.ddeb

#安装
dpkg -i linux-image-4.4.0-85-generic-dbgsym_4.4.0-85.108_amd64.ddeb

下载并安装 elfutils

elfutilsSystemtap 依赖的软件,需要安装。有的系统可能以及默认安装了,建议卸载掉,安装较新的版本。最好与安装的 Systemtap 发行版本日期较近。

elfutils资源地址

# 查看 elfutils 版本
apt-show-versions -p elfutils

# 删除elfutils
apt-get remove elfutils

#下载对应的最新版本
wget https://sourceware.org/elfutils/ftp/0.168/elfutils-0.168.tar.bz2

tar -xjf elfutils-0.168.tar.bz2

#安装
./configure

make && make install

下载并安装 Systemtap

Ubuntu 默认是安装了 Systemtap,一般默认安装的版本是2.9,此版本较旧,可能会遇到各种问题,建议安装最新版本。不建议使用 apt-get install 方式安装。

Systemtap资源地址


# 查看版本
stap -V

# 卸载 systemtap
apt-get remove systemtap

# 下载最新版本
wget https://sourceware.org/systemtap/ftp/releases/systemtap-3.1.tar.gz

tar -zxvf systemtap-3.1.tar.gz

# 安装
./configure --prefix=/opt/apps/systemtap

make && make install

安装完成后,需要配置环境变量。

export SYSTEMTAP_HOME=/opt/apps/systemtap
export PATH=$SYSTEMTAP_HOME/bin:$PATH

验证安装结果。

# 查看 systemtap 是否可用
stap -ve 'probe begin { log("hello systemtap!") exit() }'

# 查看 systemtap 与 debuginfo 结合是否可用
stap -e 'probe kernel.function("sys_open") {log("hello world") exit()}'

如果能正常打印出内容,则说明安装成功。

为了使 debug symbols 更友好的支持 gdb,需要执行以下操作。创建 debug_ko.sh 脚本。内容如下:

for file in `find /usr/lib/debug -name '*.ko' -print`
do
        buildid=`eu-readelf -n $file| grep Build.ID: | awk '{print $3}'`
        dir=`echo $buildid | cut -c1-2`
        fn=`echo $buildid | cut -c3-`
        mkdir -p /usr/lib/debug/.build-id/$dir
        ln -s $file /usr/lib/debug/.build-id/$dir/$fn
        ln -s $file /usr/lib/debug/.build-id/$dir/${fn}.debug
done

然后执行该脚本。

stap -L 'module("thinkpad_acpi").function("brightness*")' | sort

这样,在 Ubuntu 下安装 SystemTap 就完成。

第三方 Profiler


火焰图是将在 CPU 上运行的栈信息可视化。所以,要想生成火焰图,必须先收集目标程序在 CPU 上的栈信息。

Java

针对 Java,可以使用轻量级的 lightweight-java-profiler,目前只支持 hotspot JVM。只有在程序退出时(例如: System.exit(0)),才会将收集到数据写到目标文件(kill -9 pid方式结束进程是无效的)。

git clone https://github.com/dcapwell/lightweight-java-profiler

cd lightweight-java-profiler

make all
  • 注意: 可以在 src/globasl.h 中设定参数
// 每秒采样点数
static const int kNumInterrupts = 100;

// 最大采样的stack数量
static const int kMaxStackTraces = 3000;

// 一个采样stack的最大深度
static const int kMaxFramesToCapture = 128;

Nginx / Lua

针对 Nginx,推荐使用 openresty-systemtap-toolkit, 是基于 SystemTapOpenResty (包括 NginxLuaJITngx_lua等)的实时分析和诊断工具。由大神章亦春提供。这里面很多工具适用于任何 C/CPP 语言编写的程序。

它支持选择对 CPU 监控开关的控制。

  • sample-bt: 用来生成 On-CPU 火焰图的采样数据(DEMO)
  • sample-bt-off-cpu: 用来生成 Off-CPU 火焰图的采样数据(DEMO)

什么时候使用 On-CPU 火焰图,什么时候使用 Off-CPU 火焰图,取决于当前的瓶颈到底是什么。

如果是 CPU瓶颈,使用 On-CPU 火焰图。

如果是 IO 或锁瓶颈,使用 Off-CPU 火焰图。

如果无法确定,那么可以通过压测工具来确认:通过压测工具看看能否让 CPU 使用率趋于饱和,如果能,那么使用 On-CPU 火焰图.

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

如果还是确认不了,那么不妨将 On-CPUOff-CPU 两种模式都尝试一下,正常情况下它们的差异会比较大,如果两张火焰图长得差不多,那么通常认为 CPU 被其它进程抢占了。

# 安装
git clone https://github.com/openresty/openresty-systemtap-toolkit.git

生成火焰图


Java

在应用程序或Web容器中,加入以下 JVM 的配置。

export JAVA_OPTS="-Xms1024m -Xmx2048m -Xss256k -Xmn1024m -agentpath:/work/app/lightweight-java-profiler/build-64/liblagent.so"

-Xms之类的配置,根据情况调整。

然后,启动应用程序,进行请求测试,最后,正确停止应用服务(非kill -9方式)。

会在启动目录下生成一个 traces.txt 文件。这个就是采集到数据。

Nginx

# 获取 nginx work 进程id
ps -ef | grep nginx

# 进入openresty-systemtap-toolkit 启动
./sample-bt -p 21408 -t 60  -u > /tmp/a.bt

将数据转成SVG格式的火焰图

# 进入FlameGraph目录

#Java
./stackcollapse-ljp.awk < ../traces.txt | ./flamegraph.pl > ../traces.svg

#Nginx
./stackcollapse-stap.pl /tmp/a.bt > /tmp/a.cbt
./flamegraph.pl /tmp/a.cbt > /tmp/a.svg

火焰图

如果想快速查看某个包路径下的性能,可以直接用浏览器搜索快捷键进行搜索,关键字会以蓝色突出显示,便于快速定位。


参考: