找回密码
 立即注册
首页 业界区 安全 [Java/Flink/Arthas] Java 性能优化分析工具:(CPU) 火 ...

[Java/Flink/Arthas] Java 性能优化分析工具:(CPU) 火焰图

育局糊 前天 10:25
0 序言


  • 近期,工作项目上某 Flink 程序的数据处理性能问题一直萦绕心头,今日第一次尝试使用火焰图这一工具来分析性能问题。
最早项目上的 Flink 是 1.12 ,尚不支持此功能;但现在已切换到 Flink 1.15 版本,火焰图功能(自 1.13起)是被支持的了。


  • 或不多说,开始正文。
主要参考自第1篇参考文献(故部分操作试验,并未亲测),在此文的基础上,添加了一些个人实践经验。
1 概述: Java 性能优化分析工具——火焰图


  • 软件的性能分析与优化,往往需要查看 CPU 耗时, 了解瓶颈在哪里,而火焰图(flame graph) 是性能分析的利器,快速定位分析为啥 CPU 飙升。
火焰图的缘起


  • 很多人感冒发烧的时候, 往往会模仿神农氏尝百草的路子: 先尝尝抗病毒的药, 再试试抗细菌的药, 甭管家里有什么药挨个试, 什么中药西药, 瞎猫总会碰上死耗子, 如此做法自然是不可取的, 正确的做法应该是去医院验个血, 确诊后再对症下药.
  • 让我们回想一下我们一般是如何调试程序的: 通常是在没有数据的情况下依靠主观臆断来瞎蒙, 而不是考虑问题到底是什么引起的!
  • 毫无疑问, 调优程序性能问题的时候, 同样需要基于性能观测数据来对症下药。好消息是 Brendan D. Gregg 发明了火焰图
2 案例实践:火焰图可视化生成器

安装 perf 命令


  • perf(performance 的缩写) 它是 Linux 系统原生提供的性能分析工具, 会返回 CPU 正在执行的函数名以及调用栈(stack)
  1. # 安装perf命令
  2. sudo apt install linux-tools-common
复制代码

  • 在Ubuntu系统的Termial下,用 apt install 安装软件的时候,如果在未完成下载的情况下将 terminal close。此时 apt 进程可能没有结束。结果可能会发生下面的提示:

无法获得锁 /var/lib/dpkg/lock - open (11: 资源暂时不可用)
无法锁定管理目录(/var/lib/dpkg/),是否有其他进程正占用它?


  • 解决方法
Bash
  1. # 强制解锁命令
  2. sudo rm /var/cache/apt/archives/lock
  3. sudo rm /var/lib/dpkg/lock
复制代码

  • 测试安装,输入命令:perf -v
Bash
  1. # 查看版本
  2. liurenkui@ubuntu:~$ perf -v
  3. perf version 4.18.20
复制代码
下载 FlameGraph


  • Github
https://github.com/brendangregg/FlameGraph
运行 java 应用项目


  • 准备了一个非常简单的SpringBoot项目:
GitHub: GitHub - X-rapido/springboot-hello
一个非常简单的helloworld项目,方便以后在其他项目上调用演示而已。


  • 将 FlameGraph 和 springboot-hello 项目,放在一个目录中。使用 java -jar springboot-hello-0.0.1-SNAPSHOT.jar 启动项目


  • 主要是运行下面的慢方法
  1. /**
  2. * 测试慢执行
  3. */
  4. @RequestMapping("/hello/cycle")
  5. public String helloCycle(@RequestParam Integer cycle) throws InterruptedException {
  6.     DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
  7.     for (int i = 0; i < cycle; i++) {
  8.         TimeUnit.MILLISECONDS.sleep(10);
  9.         System.out.println(i + " 嗨,你好 " + LocalDateTime.now().format(dtf));
  10.     }
  11.     return "hello cycle over";
  12. }
复制代码
运行 perf 采集数据


  • 查看java进程号
运行命令: jps -l
  1. liurenkui@ubuntu:~/MyLib/Demo$ jps -l
  2. 5218 sun.tools.jps.Jps
  3. 5149 springboot-hello-0.0.1-SNAPSHOT.jar
复制代码

  • 开始采集
Bash
  1. # 命令
  2. sudo perf record -F 采集次数 -p 进程号 -g -- sleep 采集秒数
复制代码
1、执行慢方法:curl http://localhost:8080/hello/cycle?cycle=6000
2、执行采集:sudo perf record -F 99 -p 5149 -g -- sleep 30,采集完之后会在当前目录生成一个 perf.data 文件
perf record 表示采集系统事件, 没有使用 -e 指定采集事件, 则默认采集 cycles(即 CPU clock 周期), -F 99表示每秒 99 次, -p 13204 是进程号, 即对哪个进程进行分析, -g 表示记录调用栈, sleep 30 则是持续 30 秒.
-F 指定采样频率为 99Hz(每秒99次), 如果 99次 都返回同一个函数名, 那就说明 CPU 这一秒钟都在执行同一个函数, 可能存在性能问题.
运行后会产生一个庞大的文本文件. 如果一台服务器有 16 个 CPU, 每秒抽样 99 次, 持续 30 秒, 就得到 47,520 个调用栈, 长达几十万甚至上百万行.
为了便于阅读,perf record 命令可以统计每个调用栈出现的百分比, 然后从高到低排列.
  1. # 统计每个调用栈出现的百分比
  2. sudo perf report -fn --stdio
复制代码

生成 svg 火焰图


  • 首先,用 perf script 工具对 perf.data 进行解析
  1. # 生成折叠后的调用栈
  2. sudo perf script -i perf.data &> perf.unfold
复制代码
将解析出来的信息存下来, 供生成火焰图.


  • 首先用 stackcollapse-perf.pl 将 perf 解析出的内容 perf.unfold 中的符号进行折叠,最后生成 svg 图.
Bash
  1. # 生成火焰图
  2. sudo FlameGraph/stackcollapse-perf.pl perf.unfold &> perf.folded
  3. # 生成 svg
  4. sudo FlameGraph/flamegraph.pl perf.folded > perf.svg
复制代码
我们可以使用管道将上面的流程简化为一条命令:
  1. # 简化上面命令
  2. sudo perf script | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > process.svg
复制代码

打开 svg 火焰图


  • 将 svg 文件,使用浏览器打开

3 解读火焰图

3.1 火焰图的含义


  • 火焰图是基于 stack 信息生成的 SVG 图片, 用来展示 CPU 的调用栈。


  • y 轴表示调用栈,每一层都是一个函数. 调用栈越深,火焰就越高,顶部就是正在执行的函数,下方都是它的父函数.
  • x 轴表示抽样数,如果一个函数在 x 轴占据的宽度越宽,就表示它被抽到的次数多,即执行的时间长,注意, x 轴不代表时间, 而是所有的调用栈合并后, 按字母顺序排列的.


  • 火焰图就是看【顶层】的哪个【函数】占据的【宽度】最大. 只要有 “【平顶】”(plateaus), 就表示该函数可能存在【性能问题】。
  • 【颜色】没有特殊含义, 因为火焰图表示的是 CPU 的繁忙程度, 所以一般选择暖色调.
3.2 互动性

火焰图是 SVG 图片,可以与用户互动.


  • (1)鼠标悬浮
火焰的每一层都会标注函数名,鼠标悬浮时会显示完整的函数名、抽样抽中的次数、占据总抽样次数的百分比


  • (2)点击放大
在某一层点击,火焰图会水平放大,该层会占据所有宽度,显示详细信息。
左上角会同时显示 Reset Zoom,点击该链接,图片就会恢复原样.


  • (3)搜索
按下 Ctrl + F 会显示一个搜索框,用户可以输入关键词或正则表达式,所有符合条件的函数名会高亮显示.
3.3 局限

两种情况下, 无法画出火焰图, 需要修正系统行为.


  • (1)调用栈不完整
    当调用栈过深时,某些系统只返回前面的一部分(比如前10层)。
  • (2)函数名缺失
    有些函数没有名字,编译器只用内存地址来表示(比如匿名函数)。
3.4 浏览器的火焰图


  • Chrome 浏览器可以生成页面脚本的火焰图, 用来进行 CPU 分析.
  • 打开开发者工具, 切换到 Performance 面板. 然后, 点击 录制 按钮, 开始记录数据. 这时, 可以在页面进行各种操作, 然后停止”录制”,这时, 开发者工具会显示一个时间轴. 它的下方就是火焰图.


  • 浏览器的火焰图与标准火焰图有两点差异: 浏览器是倒置的(即调用栈最顶端的函数在最下方); x 轴是时间轴, 而不是抽样次数.
3.5 Flink 的火焰图(Flink 1.13+)


  • Flink 1.13还引入了一系列性能分析工具,帮助用户更好地理解和优化作业性能。
这些工具包括用于识别瓶颈节点的负载和反压可视化、分析算子热点代码的CPU火焰图以及分析State Backend状态的State访问性能指标等。
详情参见本文: Flink 火焰图章节
4 红蓝分叉火焰图


  • 幸亏有了 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%,而且代码也更加复杂的时候,问题就没那么好处理了。

2 红蓝差分火焰图简介

红蓝差分火焰图的工作原理是这样的


  • 抓取修改前的堆栈 profile1 文件
  • 抓取修改后的堆栈 profile2 文件
  • 使用 profile2 来生成火焰图. (这样栈帧的宽度就是以 profile2 文件为基准的)
  • 使用 “2-1” 的差异来对火焰图重新上色,原则是,如果栈帧在 profile2 中出现出现的次数更多,则标为红色, 否则标为蓝色,色彩是根据修改前后的差异来填充的.
这样做的目的是,同时使用了修改前后的 profile 文件进行对比,在进行功能验证测试或者评估代码修改对性能的影响时,会非常有用。


  • 新的火焰图是基于修改后的 profile 文件生成(所以栈帧的宽度仍然显示了当前的CPU消耗)。通过颜色的对比,就可以了解到系统性能差异的原因。
  • 只有对性能产生直接影响的函数才会标注颜色(比如说,正在运行的函数),它所调用的子函数不会重复标注。
3 生成红/蓝差分火焰图

作者的 GitHub 仓库 FlameGrdph 中实现了一个程序脚本,difffolded.pl 用来生成红蓝差分火焰图. 为了展示工具是如何工作的,利用刚才的方式抓取两次。


  • 第一次,执行慢方法:curl http://localhost:8080/hello/cycle?cycle=6000 ,抓取修改前的 profile1 文件
  1. # 抓取数据
  2. sudo perf record -F 99 -a -g -- sleep 30
  3. # 解析数据生成堆栈信息
  4. sudo perf script > out.stacks1
  5. # 折叠堆栈
  6. sudo FlameGraph/stackcollapse-perf.pl out.stacks1 > out.folded1
复制代码

  • 第二次,执行慢方法:curl http://localhost:8080/hello/cycle?cycle=3000,一段时间后 (或者程序代码修改后),抓取 profile 2 文件
Bash
  1. # 抓取数据
  2. sudo perf record -F 99 -a -g -- sleep 30
  3. # 解析数据生成堆栈信息
  4. sudo perf script > out.stacks2
  5. # 折叠堆栈
  6. sudo FlameGraph/stackcollapse-perf.pl out.stacks2 > out.folded2
复制代码

  • 最终,生成红蓝差分火焰图
Bash
  1. # 生成红蓝差分火焰图
  2. sudo FlameGraph/difffolded.pl out.folded1 out.folded2 | FlameGraph/flamegraph.pl > diff2.svg
复制代码
difffolded.pl 只能对 “折叠” 过的堆栈 profile 文件进行操作,折叠操作 由前面的 stackcollapse 系列脚本完成的。
5 生成 JAVA 堆栈火焰图

让我们回顾一下,刚才java代码生成的火焰图


  • 我们看到,上面乱乱麻麻堆栈信息,我们看起来也是非常的迷茫。


  • 哪些是 CPU 堆栈,哪些是Java堆栈?
  • 能不能把 Java 堆栈和 CPU 堆栈明确区分?
  • 有的,使用 jmaps 脚本,自动的为所有Java进程创建符号文件
下载,编译 perf-map


  • GitHub
GitHub - jvm-profiling-tools/perf-map-agent: A java agent to generate method mappings to use with the linux perf tool
  1. # 克隆代码
  2. git clone https://github.com/jvm-profiling-tools/perf-map-agent.git
  3. # 进入目录
  4. cd perf-map-agent/
  5. # 安装cmake、如果本地有就不用安装了
  6. sudo apt install cmake
  7. sudo apt install gcc
  8. sudo apt install gcc-c++
  9. # 编译
  10. cmake .
  11. make
复制代码
成功编译后会在out目录下生成attach-main.jar和libperfmap.so两个文件,这是获取java程序运行时符号表的关键。
配置 perf-map


  • (1)JAVA_HOME 和 AGENT_HOME
打开 /FlameGraph/jmaps 文件,其中一段儿代码如下:
  1. JAVA_HOME=${JAVA_HOME:-/usr/lib/jvm/java-8-oracle}
  2. AGENT_HOME=${AGENT_HOME:-/usr/lib/jvm/perf-map-agent}  # from https://github.com/jvm-profiling-tools/perf-map-agent
复制代码
这里表明,必须使用Java 8,需要我们手动将 AGENT_HOME 替换为刚才编译后的 per-map-agent/out/ 目录。修改如下
  1. JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
  2. AGENT_HOME=/home/liurenkui/MyLib/Demo/perf-map-agent
复制代码

  • (2)设置非root用户执行
如果当前用户不是root, 注释掉jmaps脚本中的如下代码:
  1. if [[ "$USER" != root ]]; then
  2.         echo "ERROR: not root user? exiting..."
  3.         exit
  4. fi
复制代码

  • (3)sudo权限生成文件
为避免执行./jmaps脚本出错,chown: changing ownership of '/tmp/perf-xxx.map': Operation not permitted
将jmaps中的代码
  1. if [[ -e "$mapfile" ]]; then
  2.         chown root $mapfile
  3.         chmod 666 $mapfile
  4. else
复制代码
改为:
  1. if [[ -e "$mapfile" ]]; then
  2.         sudo chown root $mapfile
  3.         sudo chmod 666 $mapfile
  4. else
复制代码

  • (4)修改 rm 权限
重新执行会删除临时文件,非root权限不能删除,增加一个sudo
  1. # 修改前
  2. [[ -e $mapfile ]] && rm $mapfile
  3. # 修改后
  4. [[ -e $mapfile ]] && sudo rm $mapfile
复制代码
生成java火焰图


  • 用 jmaps 为 java 进程创建符号表 生成 java 堆栈的火焰图
  1. # 采集
  2. sudo perf record -F 99 -ag -p 进程ID -- sleep 30; ./FlameGraph/jmaps
  3. # 折叠堆栈
  4. sudo perf script > out.stacks
  5. # 生成svgsudo cat out.stacks | ./FlameGraph/stackcollapse-perf.pl | grep -v cpu_idle | ./FlameGraph/flamegraph.pl --color=java --hash > out.svg
复制代码
示例图:

查看火焰图


绿色的部分表示 Java 堆栈信息。(非常直观)


  • 特别提示


  • 如果你的火焰图中,没有顶部栈的信息,在启动java项目时,请加上JVM参数:-XX:+PreserveFramePointer
可以写入一个shell脚本,一劳永逸:
  1. # 查看
  2. liurenkui@ubuntu:~/MyLib/Demo$ cat build.sh
  3. #!/bin/sh
  4. perf record -F 99 -ag -p `jps -l | grep springboot | awk '{print $1}'` -- sleep 30; ./FlameGraph/jmaps
  5. perf script > out.stacks
  6. cat out.stacks | ./FlameGraph/stackcollapse-perf.pl | grep -v cpu_idle | ./FlameGraph/flamegraph.pl --color=java --hash > out.svg
  7. # 修改权限
  8. liurenkui@ubuntu:~/MyLib/Demo$ chmod +x build.sh
  9. # 执行
  10. liurenkui@ubuntu:~/MyLib/Demo$ sudo ./build.sh
复制代码
T 最佳实践

Arthas 火焰图


  • 如果你了解 Alibaba Arthas,那么 3.1.5 版本中支持火焰图,快速定位应用热点,你一定不要错过。
https://github.com/alibaba/arthas
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
Apache Flink 支持开启火焰图


  • 从 Apache Flink 1.13 版本开始,火焰图成为 Flink 的原生支持功能。
通过启用 rest.flamegraph.enabled 配置项,用户可以生成火焰图来直观分析性能瓶颈
需要在 Flink 的配置文件 flink-conf.yaml (华为云 DLI 产品: 【优化参数】) 中添加以下配置
  1. rest.flamegraph.enabled=true
复制代码


  • 使用建议


  • 火焰图功能默认是【关闭】的。
因为采样堆栈跟踪可能会对生产环境产生一定的性能影响
建议在开发和预生产环境中启用此功能,而在生产环境中将其视为【实验性功能】。


  • 火焰图类型
火焰图支持以下几种模式:


  • On-CPU 火焰图:显示处于 RUNNABLE 或 NEW 状态的线程。
  • Off-CPU 火焰图: 可视化阻塞调用,显示处于 TIMED_WAITING、WAITING 或 BLOCKED 状态的线程。
  • 混合模式火焰图: 结合所有线程状态的堆栈跟踪生成。


  • 注意事项


  • 火焰图的采样过程仅在 JVM 内部进行。
因此只能看到 Java 运行时的方法调用,无法捕获系统调用
此外,【默认情况】下,火焰图是在【算子级别】构建的,所有【并行任务的堆栈跟踪】会被合并。
如果某个方法在某些任务中【占用资源】较多,而在【其他任务】中占用较少,可能会【被平均化】,从而【掩盖瓶颈】。


  • 从 Flink 1.17 版本开始,支持单并发级别的火焰图,可针对【特定子任务】进行更精细的分析。


  • 推荐文献


  • Apache Flink - 火焰图
https://nightlies.apache.org/flink/flink-docs-master/zh/docs/ops/debugging/flame_graphs/
Y 推荐文献


  • async-profiler


  • https://github.com/jvm-profiling-tools/async-profiler/releases
X 参考文献


  • 如何用火焰图进行 Java 性能分析,这一篇文章就够了 - CSDN
  • 大促压测 火焰图诊断Java CPU瓶颈-哔哩哔哩 【推荐】
  • Linux perf Examples - brendangregg.com
  • 红蓝分叉火焰图:Differential Flame Graphs - brendangregg.com
  • 如何读懂火焰图?:如何读懂火焰图? - 阮一峰的网络日志
  • 使用火焰图做性能分析 - neoremind.com
  • Linux下用火焰图进行性能分析 - CSDN
    本文作者:        千千寰宇   
    本文链接:         https://www.cnblogs.com/johnnyzen   
    关于博文:评论和私信会在第一时间回复,或直接私信我。   
    版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA     许可协议。转载请注明出处!
    日常交流:大数据与软件开发-QQ交流群: 774386015        【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!   

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册