如何用火焰图进行 Java 性能分析,这一篇文章就够了 |
您所在的位置:网站首页 › 火焰的火焰 › 如何用火焰图进行 Java 性能分析,这一篇文章就够了 |
关注Java方面,移步最下面:生成 JAVA 堆栈火焰图 软件的性能分析,往往需要查看 CPU 耗时, 了解瓶颈在哪里,而火焰图(flame graph) 是性能分析的利器,快速定位分析为啥 CPU 飙升。 一、火焰图简介很多人感冒发烧的时候, 往往会模仿神农氏尝百草的路子: 先尝尝抗病毒的药, 再试试抗细菌的药, 甭管家里有什么药挨个试, 什么中药西药, 瞎猫总会碰上死耗子, 如此做法自然是不可取的, 正确的做法应该是去医院验个血, 确诊后再对症下药. 让我们回想一下我们一般是如何调试程序的: 通常是在没有数据的情况下依靠主观臆断来瞎蒙, 而不是考虑问题到底是什么引起的! 毫无疑问, 调优程序性能问题的时候, 同样需要对症下药. 好消息是 Brendan D. Gregg 发明了火焰图 二、火焰图可视化生成器实战 1、安装 perf 命令perf(performance 的缩写) 它是 Linux 系统原生提供的性能分析工具, 会返回 CPU 正在执行的函数名以及调用栈(stack) Bash # 安装perf命令 sudo apt install linux-tools-common在Ubuntu系统的Termial下,用 apt install 安装软件的时候,如果在未完成下载的情况下将 terminal close。此时 apt 进程可能没有结束。结果可能会发生下面的提示: 解决方法 Bash # 强制解锁命令 sudo rm /var/cache/apt/archives/lock sudo rm /var/lib/dpkg/lock测试安装,输入命令:perf -v Bash # 查看版本 liurenkui@ubuntu:~$ perf -v perf version 4.18.20 2、下载 FlameGraphGitHub源码:GitHub - brendangregg/FlameGraph: Stack trace visualizer 3、运行 java 项目我准备了一个非常简单的SpringBoot项目,GitHub地址:GitHub - X-rapido/springboot-hello: 一个非常简单的helloworld项目,方便以后在其他项目上调用演示而已。 将 FlameGraph 和 springboot-hello 项目,放在一个目录中。使用 java -jar springboot-hello-0.0.1-SNAPSHOT.jar 启动项目
主要是运行下面的慢方法 /** * 测试慢执行 */ @RequestMapping("/hello/cycle") public String helloCycle(@RequestParam Integer cycle) throws InterruptedException { DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); for (int i = 0; i perf.unfold将解析出来的信息存下来, 供生成火焰图. 首先用 stackcollapse-perf.pl 将 perf 解析出的内容 perf.unfold 中的符号进行折叠,最后生成 svg 图. Bash # 生成火焰图 sudo FlameGraph/stackcollapse-perf.pl perf.unfold &> perf.folded # 生成 svg sudo FlameGraph/flamegraph.pl perf.folded > perf.svg我们可以使用管道将上面的流程简化为一条命令 Bash # 简化上面命令 sudo perf script | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > process.svg将 svg 文件,使用浏览器打开。 火焰图是基于 stack 信息生成的 SVG 图片, 用来展示 CPU 的调用栈。 y 轴表示调用栈,每一层都是一个函数. 调用栈越深,火焰就越高,顶部就是正在执行的函数,下方都是它的父函数. x 轴表示抽样数,如果一个函数在 x 轴占据的宽度越宽,就表示它被抽到的次数多,即执行的时间长,注意, x 轴不代表时间, 而是所有的调用栈合并后, 按字母顺序排列的. 火焰图就是看顶层的哪个函数占据的宽度最大. 只要有 “平顶”(plateaus), 就表示该函数可能存在性能问题。 颜色没有特殊含义, 因为火焰图表示的是 CPU 的繁忙程度, 所以一般选择暖色调. 2、互动性火焰图是 SVG 图片,可以与用户互动. (1)鼠标悬浮 火焰的每一层都会标注函数名,鼠标悬浮时会显示完整的函数名、抽样抽中的次数、占据总抽样次数的百分比 (2)点击放大 在某一层点击,火焰图会水平放大,该层会占据所有宽度,显示详细信息。 左上角会同时显示 Reset Zoom,点击该链接,图片就会恢复原样. (3)搜索 按下 Ctrl + F 会显示一个搜索框,用户可以输入关键词或正则表达式,所有符合条件的函数名会高亮显示. 3、局限两种情况下, 无法画出火焰图, 需要修正系统行为. (1)调用栈不完整 当调用栈过深时,某些系统只返回前面的一部分(比如前10层)。 (2)函数名缺失 有些函数没有名字,编译器只用内存地址来表示(比如匿名函数)。 4、浏览器的火焰图Chrome 浏览器可以生成页面脚本的火焰图, 用来进行 CPU 分析. 打开开发者工具, 切换到 Performance 面板. 然后, 点击 录制 按钮, 开始记录数据. 这时, 可以在页面进行各种操作, 然后停止”录制”,这时, 开发者工具会显示一个时间轴. 它的下方就是火焰图. ![]() 浏览器的火焰图与标准火焰图有两点差异: 浏览器是倒置的(即调用栈最顶端的函数在最下方); x 轴是时间轴, 而不是抽样次数. 四、红蓝分叉火焰图幸亏有了 CPU 火焰图(flame graphs), CPU 使用率的问题一般都比较好定位. 但要处理性能回退问题, 就要在修改前后或者不同时期和场景下的火焰图之间, 不断切换对比, 来找出问题所在, 这感觉就是像在太阳系中搜寻冥王星. 虽然, 这种方法可以解决问题, 但我觉得应该会有更好的办法. 所以, 下面就隆重介绍 红/蓝差分火焰图(red/blue differential flame graphs) 参考:Differential Flame Graphs 1、红蓝差分火焰图示例这是一副交互式 SVG 格式图片. 图中使用了两种颜色来表示状态, 红色表示增长, 蓝色表示衰减. 这张火焰图中各火焰的形状和大小都是和第二次抓取的 profile 文件对应的 CPU 火焰图是相同的. (其中,y 轴表示栈的深度,x 轴表示样本的总数,栈帧的宽度表示了 profile 文件中该函数出现的比例,最顶层表示正在运行的函数,再往下就是调用它的栈). 在下面这个案例展示了,在系统升级后,一个工作载荷的 CPU 使用率上升了。下面是对应的 CPU 火焰图(SVG 格式) 通常,在标准的火焰图中栈帧和栈塔的颜色是随机选择的,而在红/蓝差分火焰图中,使用不同的颜色来表示两个 profile 文件中的差异部分. 在第二个 profile 中 deflate_slow() 函数以及它后续调用的函数运行的次数要比前一次更多,所以在上图中这个栈帧被标为了红色。可以看出问题的原因是 ZFS 的压缩功能被启用了, 而在系统升级前这项功能是关闭的. 这个例子过于简单,我们甚至可以不用差分火焰图也能分析出来,但想象一下,如果是在分析一个微小的性能下降,比如说小于5%,而且代码也更加复杂的时候,问题就没那么好处理了。 红蓝差分火焰图的工作原理是这样的 抓取修改前的堆栈 profile1 文件 抓取修改后的堆栈 profile2 文件 使用 profile2 来生成火焰图. (这样栈帧的宽度就是以 profile2 文件为基准的) 使用 “2-1” 的差异来对火焰图重新上色,原则是,如果栈帧在 profile2 中出现出现的次数更多,则标为红色, 否则标为蓝色,色彩是根据修改前后的差异来填充的. 这样做的目的是,同时使用了修改前后的 profile 文件进行对比,在进行功能验证测试或者评估代码修改对性能的影响时,会非常有用。 新的火焰图是基于修改后的 profile 文件生成(所以栈帧的宽度仍然显示了当前的CPU消耗)。通过颜色的对比,就可以了解到系统性能差异的原因。 只有对性能产生直接影响的函数才会标注颜色(比如说,正在运行的函数),它所调用的子函数不会重复标注。 3、生成红/蓝差分火焰图作者的 GitHub 仓库 FlameGrdph 中实现了一个程序脚本,difffolded.pl 用来生成红蓝差分火焰图. 为了展示工具是如何工作的,利用刚才的方式抓取两次。 第一次,执行慢方法:curl http://localhost:8080/hello/cycle?cycle=6000,抓取修改前的 profile1 文件 Bash # 抓取数据 sudo perf record -F 99 -a -g -- sleep 30 # 解析数据生成堆栈信息 sudo perf script > out.stacks1 # 折叠堆栈 sudo FlameGraph/stackcollapse-perf.pl out.stacks1 > out.folded1第二次,执行慢方法:curl http://localhost:8080/hello/cycle?cycle=3000,一段时间后 (或者程序代码修改后),抓取 profile 2 文件 Bash # 抓取数据 sudo perf record -F 99 -a -g -- sleep 30 # 解析数据生成堆栈信息 sudo perf script > out.stacks2 # 折叠堆栈 sudo FlameGraph/stackcollapse-perf.pl out.stacks2 > out.folded2最终,生成红蓝差分火焰图 Bash # 生成红蓝差分火焰图 sudo FlameGraph/difffolded.pl out.folded1 out.folded2 | FlameGraph/flamegraph.pl > diff2.svgdifffolded.pl 只能对 “折叠” 过的堆栈 profile 文件进行操作,折叠操作 由前面的 stackcollapse 系列脚本完成的。 五、生成 JAVA 堆栈火焰图让我们回顾一下,刚才java代码生成的火焰图 ![]() 我们看到,上面乱乱麻麻堆栈信息,我们看起来也是非常的迷茫。 哪些是 CPU 堆栈,哪些是Java堆栈? 能不能把 Java 堆栈和 CPU 堆栈明确区分? 有的,使用 jmaps 脚本,自动的为所有Java进程创建符号文件 1、下载,编译 perf-mapGitHub地址:GitHub - jvm-profiling-tools/perf-map-agent: A java agent to generate method mappings to use with the linux `perf` tool Bash # 克隆代码 git clone https://github.com/jvm-profiling-tools/perf-map-agent.git # 进入目录 cd perf-map-agent/ # 安装cmake、如果本地有就不用安装了 sudo apt install cmake sudo apt install gcc sudo apt install gcc-c++ # 编译 cmake . make成功编译后会在out目录下生成attach-main.jar和libperfmap.so两个文件,这是获取java程序运行时符号表的关键。 2、配置 perf-map(1)JAVA_HOME 和 AGENT_HOME 打开 /FlameGraph/jmaps 文件,其中一段儿代码如下: Python JAVA_HOME=${JAVA_HOME:-/usr/lib/jvm/java-8-oracle} AGENT_HOME=${AGENT_HOME:-/usr/lib/jvm/perf-map-agent} # from https://github.com/jvm-profiling-tools/perf-map-agent这里表明,必须使用Java8,需要我们手动将 AGENT_HOME 替换为刚才编译后的 per-map-agent/out/ 目录。修改如下 Python JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 AGENT_HOME=/home/liurenkui/MyLib/Demo/perf-map-agent(2)设置非root用户执行 如果当前用户不是root, 注释掉jmaps脚本中的如下代码: Python if [[ "$USER" != root ]]; then echo "ERROR: not root user? exiting..." exit fi(3)sudo权限生成文件 避免执行./jmaps脚本出错,chown: changing ownership of '/tmp/perf-xxx.map': Operation not permitted 将jmaps中的代码 Python if [[ -e "$mapfile" ]]; then chown root $mapfile chmod 666 $mapfile else改为: Python if [[ -e "$mapfile" ]]; then sudo chown root $mapfile sudo chmod 666 $mapfile else(4)修改 rm 权限 重新执行会删除临时文件,非root权限不能删除,增加一个sudo Bash # 修改前 [[ -e $mapfile ]] && rm $mapfile # 修改后 [[ -e $mapfile ]] && sudo rm $mapfile 3、生成java火焰图用 jmaps 为 java 进程创建符号表 生成 java 堆栈的火焰图 Bash # 采集 sudo perf record -F 99 -ag -p 进程ID -- sleep 30; ./FlameGraph/jmaps # 折叠堆栈 sudo perf script > out.stacks # 生成svgsudo cat out.stacks | ./FlameGraph/stackcollapse-perf.pl | grep -v cpu_idle | ./FlameGraph/flamegraph.pl --color=java --hash > out.svg示例图 ![]() 绿色的部分表示 Java 堆栈信息。非常的直观。 补充1、如果你的火焰图中,没有顶部栈的信息,在启动java项目时,请加上JVM参数:-XX:+PreserveFramePointer 2、可以写入一个shell脚本,一劳永逸 Bash # 查看 liurenkui@ubuntu:~/MyLib/Demo$ cat build.sh #!/bin/sh perf record -F 99 -ag -p `jps -l | grep springboot | awk '{print $1}'` -- sleep 30; ./FlameGraph/jmaps perf script > out.stacks cat out.stacks | ./FlameGraph/stackcollapse-perf.pl | grep -v cpu_idle | ./FlameGraph/flamegraph.pl --color=java --hash > out.svg # 修改权限 liurenkui@ubuntu:~/MyLib/Demo$ chmod +x build.sh # 执行 liurenkui@ubuntu:~/MyLib/Demo$ sudo ./build.sh 六、Arthas 火焰图(重点推荐)如果你了解 Alibaba Arthas,那么 3.1.5版本中支持火焰图,快速定位应用热点,你一定不要错过:https://github.com/alibaba/arthas/issues/951 Arthas解放你的双手,直接可用,相当的方便 async-profiler:https://github.com/jvm-profiling-tools/async-profiler arthas使用profiler生成火焰图:https://alibaba.github.io/arthas/profiler.html 七、参考英文wiki:Linux perf Examples (阮一峰)如何读懂火焰图?:如何读懂火焰图? - 阮一峰的网络日志 使用火焰图做性能分析:http://neoremind.com/2017/09/使用火焰图做性能分析 Linux下用火焰图进行性能分析:Linux下用火焰图进行性能分析_flamegraph-diff_CHENG Jian的博客-CSDN博客 红蓝分叉火焰图:Differential Flame Graphs |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |