[译]Java火焰图

在我的前一天转的一篇文章中([转]go's march to low latency gc),Twitch的Rhys Hiltner使用火焰图(FlameGraph)来分析Go程序的性能给我留下了深刻的印象,它使用Brendan Gregg创建的火焰图工具生成直观的图像,很方便的分析Go的各个方法占用的CPU的时间。

因为我的一部分时间还是使用Java开发,所以就想到有没有Java相关的工具生成火焰图呢?答案当然是肯定的,而且它更早的应用于Java程序的性能分析。

火焰图工具的作者Brendan Gregg专门写了一篇文章:Java Flame Graphs,介绍如何生成火焰图的。这就是本文要翻译的文章。

Netflix深度使用了火焰图工具,他们专门写了一篇文章Java in Flames,介绍他们是如何使用的,而且前段时间他们又写了一篇文章: 如何使用火焰图分析性能,每天为Netflix节省一千三百万分钟的计算时间,文中介绍了他们如何使用火焰题找到耗时的问题所在,为公司节省了大量的时间和金钱。(Brendan Gregg就是Netflix的员工,他的站点觉得是一个值得阅读的地方,还有很多其它性能相关的专题,比如Linux性能工具 http://www.brendangregg.com/linuxperf.html)

既然火焰图这么有效,你难道不想了解一下吗?

以下是Brendan Gregg文章的翻译。

Java火焰图是一个新的方法来可视化CPU的使用情况,本文中我会展示如何使用免费开源的工具来创建它:Google的lightweight-java-profiler 以及我的火焰图工具。希望有一天火焰图能成为所有的Java Profiler工具的标准特性,就像现在的调用图(call tree graph)一样。

更新信息(2015年8月) Netflix的博客的文章Java in Flames展示了最新的最好的产生Java火焰图的方法,使用 Linux perf_events来一起显示Java和系统代码(system code paths)。我也在Java CPU Flame Graphs一文中做了介绍。本文仅分析Java相关的方法,包含一些这些链接中的警告(caveats)。

下图显示了使用vert.x实现的一个简单的JavaScript的大图:



惊艳! 鼠标放上去还能看细节。它是SVG格式的。

Y轴是栈的深度(stack depth),X轴是所有的采样点的集合,每个方框代表一个栈帧(stack frame)。 颜色没有意义,只是随机的选取的。左右顺序也不重要。

你可以从最宽的帧看起,从底往上看,帧上的分叉代表不同的代码路径。我的文章CPU flame graphs有很详细的解释,这个PPTflame graphs presentation 中业余介绍。一旦你掌握这些窍门,你可以快速识别和量化的CPU使用率。

左边最高的高是Mozilla Rhino JavaScript engine,它吃掉了 42.70% CPU (可以查看最低部的mozilla frame; 百分比包含它上面所有的子帧)。Java/vert.x 不需要运行这个引擎,释放这些CPU资源的话,可以增加大约一倍的性能。

其它的细节也很有趣:其它最大的CPU占用是write0(), 大约31.99%, 这是可取的 – vert.x要响应请求,发送response。处理调优write0,要想提升性能,就得检查和减少其它代码的CPU占用。移除Rhino是最好的提升方法。

通过收集不同天的或者不同版本的火焰图,你可以快速地比较它们的性能,提供量化的数据。

Profile 数据收集

火焰图可以将采样的正在CPU上运行的栈信息可视化。你可以使用任意的可提供给你stack trace的profiler工具,我最开始使用hprof进行采样,但是发现它有些问题

在这个例子中我使用Jeremy Manson开发的lightweight-java-profiler 。它是一个精确的profiling技术的开源演示版本,并不是一个产品级的产品,所以需要一些手工操作,以下是我的步骤:

1、下载软件

1
2
svn checkout http://lightweight-java-profiler.googlecode.com/svn/trunk/ lightweight-java-profiler-read-only
cd lightweight-java-profiler-read-only

2、修改Makefile

我使用vi编辑Makefile,设置BITS为64, 并且为我的系统增加include路径`。 我的改变如下:

1
2
3
4
5
6
7
8
4c4
< BITS?=32
---
> BITS?=64
49c49
< INCLUDES=-I$(JAVA_HOME)/$(HEADERS) -I$(JAVA_HOME)/$(HEADERS)/$(UNAME)
---
> INCLUDES=-I$(JAVA_HOME)/$(HEADERS) -I$(JAVA_HOME)/$(HEADERS)/$(UNAME) -I/usr/include/x86_64-linux-gnu

3、编译软件

1
make all

4、设置agent
在运行Java程序的时候我加上了下面的设置:

1
-agentpath:/usr/local/lightweight-java-profiler-read-only/build-64/liblagent.so

Java开始运行时采样开始,程序结束时采样结束,采样数据会写入到traces.txt文件中,类似hprof的格式。火焰图工具会读取这个文件。

当然运行这个采样会有些开销,对于我的程序来说可以忽略不计(~1% 请求速率的降低,7%CPU的增长), 但是你的情况可能会不同。稍后会介绍如何检查采样速率。

火焰图

现在将traces.txt处理成火焰图:

1
2
3
git clone http://github.com/brendangregg/FlameGraph
cd FlameGraph
./stackcollapse-ljp.awk < ../traces.txt | ./flamegraph.pl > ../traces.svg

stackcollapse-ljp.awk是一个awk脚本,可以转换lightweight-java-profiler的输出为火焰图工具所需的格式(每栈一行)。 flamegraph.pl脚本有一些选项设置(-h查看选项列表),可以定制输出,包括改变标题。

现在可以在浏览器中打开traces.svg文件。

定制profiler

值得注意的是你可以配置lightweight-java-profiler的一些选项,打开src/globals.h文件:

1
2
3
4
5
6
7
8
// Number of times per second that we profile
static const int kNumInterrupts = 100;
// Maximum number of stack traces
static const int kMaxStackTraces = 3000;
// Maximum number of frames to store from the stack traces sampled.
static const int kMaxFramesToCapture = 128;

我可能倾向将采样速率从每秒100次降到50次,甚至更低,尤其是当我想长时间搜集数据的时候。我也会增大最大栈帧数,这样火焰图就能显示全栈信息。

Richard Warburton也写了一个profiler: honest-profiler,也是建立在相同的全精确性能分析技术之上(accurate profiling technique),就像lightweight-java-profiler一样,你也需要自己编译才能使用。

这些profiler只是查看Java内部的性能,我理想的profiler应该包含用户栈信息和内核栈信息,这能让我们集可以分析JVM GC的代码路径,也可以分析其它的JVM内部的性能以及内核的性能。

火焰图已经在其它领域证明了它的用处,包括内核的性能,Node.JS, Ruby, Perl等等, 可以查看我的更新章节以了解最新的信息。


参考文档

以下是译者增加的其它参考资料: