當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > 架構(gòu)師社區(qū)
[導(dǎo)讀]來(lái)源:https://zhenbianshu.github.io/2019/04/application_debug_tools_flamegraph.html|前言工具的進(jìn)化一直是人類(lèi)生產(chǎn)力進(jìn)步的標(biāo)志,合理使用工具能大大提高我們的工作效率,遇到問(wèn)題時(shí),合理使用工具更能加快問(wèn)題排...



| 前言

工具的進(jìn)化一直是人類(lèi)生產(chǎn)力進(jìn)步的標(biāo)志,合理使用工具能大大提高我們的工作效率,遇到問(wèn)題時(shí),合理使用工具更能加快問(wèn)題排查的進(jìn)度。這也是我為什么非常喜歡 shell 的原因,它豐富的命令行工具集加管道特性處理起文本數(shù)據(jù)集來(lái)真的精準(zhǔn)而優(yōu)雅,讓人迷醉。


但很多時(shí)候文本的表現(xiàn)力非常有限,可以說(shuō)匱乏,表達(dá)絕對(duì)值時(shí),自然是無(wú)往不利,但在展示相對(duì)值時(shí),就有些捉襟見(jiàn)肘了,就更不用說(shuō)多維數(shù)據(jù)了。


我們用 shell 可以非常快速地查詢(xún)出文本內(nèi)的累加值、最大值等,但一遇到兩組值的相關(guān)性分析時(shí),就束手無(wú)策了。這個(gè)時(shí)候,就需要使用另一種分析工具 –圖了,如散點(diǎn)圖就能很清晰地展示相關(guān)性。


今天就準(zhǔn)備介紹一種圖,火焰圖,之前組內(nèi)大神分享過(guò)它的使用辦法,但我之后很久都沒(méi)有用過(guò),以至于對(duì)它沒(méi)有什么深刻印象,最近排查我們 Java 應(yīng)用負(fù)載問(wèn)題時(shí)試用了一下,這才對(duì)它的用途有了點(diǎn)心得。


| 介紹

引子

在排查性能問(wèn)題時(shí),我們通常會(huì)把線(xiàn)程棧 dump 出來(lái),然后使用grep --no-group-separator -A 1 java.lang.Thread.State jstack.log | awk 'NR%2==0' | sort | uniq -c | sort -nr類(lèi)似的 shell 語(yǔ)句,查看大多數(shù)線(xiàn)程棧都在干什么。而由線(xiàn)程棧的出現(xiàn)頻率,來(lái)推斷 JVM 內(nèi)耗時(shí)最多的調(diào)用。


至于其原理,設(shè)想廣場(chǎng)上有一個(gè)大屏幕在不停地播放各種廣告。如果我們隨機(jī)對(duì)大屏幕拍照,次數(shù)多了,統(tǒng)計(jì)照片中各個(gè)廣告出現(xiàn)的頻率,基本可以得出每個(gè)廣告的播放時(shí)長(zhǎng)占比了。


而我們應(yīng)用的資源就像大屏幕,每次調(diào)用就像是播放一次廣告,統(tǒng)計(jì) dump 出的線(xiàn)程棧出現(xiàn)比例,也就基本能看出線(xiàn)程棧的耗時(shí)占比,雖然有誤差,但是多次統(tǒng)計(jì)下應(yīng)該差不了多少。這也就是為什么有些家長(zhǎng)每次進(jìn)孩子房間都發(fā)現(xiàn)孩子在看系統(tǒng)桌面后以為孩子平時(shí)喜歡對(duì)著桌面發(fā)呆的原因。:)


2444  at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1200)
1587  at sun.misc.Unsafe.park(Native Method)
795  at java.security.Provider.getService(Provider.java:1035)
293  at java.lang.Object.wait(Native Method)
292  at java.lang.Thread.sleep(Native Method)
73  at org.apache.logging.log4j.core.layout.TextEncoderHelper.copyDataToDestination(TextEncoderHelper.java:61)
71  at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
70  at java.lang.Class.forName0(Native Method)
54  at org.apache.logging.log4j.core.appender.rolling.RollingFileManager.checkRollover(RollingFileManager.java:217)
但是這樣有些問(wèn)題,首先寫(xiě) shell 挺費(fèi)事的,另外如果我想查看自棧頂?shù)诙€(gè)棧的最多調(diào)用,即使修改了 shell 命令,結(jié)果也不直觀。


產(chǎn)生這個(gè)問(wèn)題的主要原因是,我們的線(xiàn)程棧是有調(diào)用關(guān)系的,即我們需要考慮線(xiàn)程棧的調(diào)用鏈和出現(xiàn)頻率兩個(gè)維度,而單一的文本表現(xiàn)這兩種維度比較困難,所以,著名性能分析大師 brendan gregg 就提出了火焰圖。


介紹

火焰圖,因其形似火焰而得名,其開(kāi)源代碼地址:


https://github.com/brendangregg/FlameGraph


它是一種 svg 可交互式圖形,我們通過(guò)點(diǎn)擊和鼠標(biāo)指向可以展示出更多的信息。下圖就是一個(gè)典型的火焰圖,從結(jié)構(gòu)上,它是由多個(gè)大小和顏色各異的方塊構(gòu)成,每個(gè)方塊上都有字符,它們底部連接在一塊,組成火焰的基底,頂部分出許多”小火苗”。


一款性能調(diào)優(yōu)利器?—?火焰圖


當(dāng)我們點(diǎn)擊方塊時(shí),圖片會(huì)從我們點(diǎn)擊的方塊為基底向上展開(kāi),而我們鼠標(biāo)指向方塊時(shí),會(huì)展示出方塊的詳細(xì)說(shuō)明。


特性

介紹火焰圖的分析前,我們要首先說(shuō)明它的特性:


  • 由底部到頂部可以追溯一個(gè)唯一的調(diào)用鏈,下面的方塊是上面方塊的父調(diào)用。
  • 同一父調(diào)用的方塊從左到右以字母序排列。
  • 方塊上的字符表示一個(gè)調(diào)用名稱(chēng),括號(hào)內(nèi)是火焰圖指向的調(diào)用在火焰圖中出現(xiàn)的次數(shù)和這個(gè)方塊占最底層方塊的寬度百分比。
  • 方塊的顏色沒(méi)有實(shí)際意義,相鄰方塊的顏色差只為了便于查看。

分析

那么,給我們一張火焰圖,我們?cè)趺茨芸闯鱿到y(tǒng)哪里有問(wèn)題呢?


由上文中的火焰圖特性特性,查看火焰圖時(shí),我們最主要的關(guān)注點(diǎn)要放在方塊的寬度上,因?yàn)閷挾却砹苏{(diào)用棧在全局出現(xiàn)的次數(shù),次數(shù)代表著出現(xiàn)頻率,而頻率也就可以說(shuō)明耗時(shí)。


但是觀察火焰圖底部或中部方塊的寬度占比意義不大,如上面的火焰圖,中部的do_redirections函數(shù)寬度是 24.87%,也就是說(shuō)它耗用了整個(gè)應(yīng)用近四分之一的時(shí)間,但是真正消耗時(shí)間的并不是 do_redirections 函數(shù),而是 do_redirections 內(nèi)部又調(diào)用的其他函數(shù),而它的子調(diào)用分為了很多個(gè),每個(gè)調(diào)用的耗時(shí)并沒(méi)有異常。


我們更應(yīng)該關(guān)注的是火焰圖頂部的一些 “平頂山”,頂部說(shuō)明它沒(méi)有子調(diào)用,方塊寬說(shuō)明它耗時(shí)長(zhǎng),長(zhǎng)時(shí)間 hang 住,或者被非常頻率地調(diào)用,這種方塊指向的調(diào)用才是性能問(wèn)題的罪魁禍?zhǔn)住?


找到了異常調(diào)用,直接優(yōu)化它,或者再根據(jù)火焰圖的調(diào)用鏈層層向下,找到我們的業(yè)務(wù)代碼進(jìn)行優(yōu)化,也就大功告成。


應(yīng)用場(chǎng)景

每種工具都有其適合的應(yīng)用場(chǎng)景,火焰圖則適合用在:


  • 代碼循環(huán)分析:如果代碼中有很大的循環(huán)或死循環(huán)代碼,那么從火焰圖的頂部或接近項(xiàng)部的地方會(huì)有很明顯的”平頂”,表示代碼頻繁地在某個(gè)線(xiàn)程棧上下切換。但需要注意的是,如果循環(huán)的總耗時(shí)不長(zhǎng),在火焰圖上不會(huì)很明顯。
  • IO 瓶頸/鎖分析:在我們的應(yīng)用代碼中,我們的調(diào)用普遍都是同步的,也就是說(shuō)在進(jìn)行網(wǎng)絡(luò)調(diào)用、文件 I/O 操作或未成功獲得鎖時(shí),線(xiàn)程會(huì)停留在某個(gè)調(diào)用上等待 I/O 響應(yīng)或鎖,如果這個(gè)等待非常耗時(shí),會(huì)導(dǎo)致線(xiàn)程在某個(gè)調(diào)用上一直 hang 住,這在火焰圖上表現(xiàn)得會(huì)非常清晰。與此相對(duì)的是,我們應(yīng)用線(xiàn)程構(gòu)成的火焰圖無(wú)法準(zhǔn)確地表達(dá) CPU 的消耗,因?yàn)閼?yīng)用線(xiàn)程內(nèi)沒(méi)有系統(tǒng)的調(diào)用棧,在應(yīng)用線(xiàn)程棧 hang 住時(shí),CPU 可能去做其他事了,導(dǎo)致我們看到耗時(shí)很長(zhǎng),而 CPU 卻很閑。
  • 火焰圖倒置分析全局代碼:火焰圖倒置有時(shí)也會(huì)很實(shí)用,如果我們的代碼 N 個(gè)不同的分支都調(diào)用某一方法,倒置后,所有棧頂相同的調(diào)用被合并在一塊,我們就能看出這個(gè)方法的總耗時(shí),也就很容易評(píng)估出優(yōu)化這個(gè)方法的收益。

| 實(shí)現(xiàn)

既然火焰圖這么強(qiáng)大,那么我們?cè)撛趺磳?shí)現(xiàn)呢?


生成工具

brendan gregg 大神已經(jīng)把生成火焰圖的方法用 perl 實(shí)現(xiàn)了,開(kāi)源代碼就在上文的 Github 倉(cāng)庫(kù)中,根目錄下的flamegraph.pl文件就是可執(zhí)行的 perl 文件了。


這個(gè)命令還可以傳入各種參數(shù),支持我們修改火焰圖的顏色、大小等 。


但 flamegraph.pl 只能處理特定格式的文件,像:


a;b;c 12
a;d 3
b;c 3
z;d 5
a;c;e 3
前面是調(diào)用鏈,每個(gè)調(diào)用之間用;隔開(kāi),每行后面的數(shù)字是調(diào)用棧出現(xiàn)的次數(shù)。


如上面的數(shù)據(jù),用 flamegraph.pl 生成的火焰圖如下圖:


一款性能調(diào)優(yōu)利器?—?火焰圖


數(shù)據(jù)準(zhǔn)備

至于我們的 jstack 信息如何被處理成上面的格式,大神則為常見(jiàn)的 dump 格式都提供了工具,像stackcollapse-perf.pl可以處理perf命令的輸出,stackcollapse-jstack.pl處理jstack輸出,stackcollapse-gdb.pl處理 gdb 輸出的棧等。


也可以用 shell 簡(jiǎn)單地實(shí)現(xiàn)一下 jstack 的處理方式:


grep -v -P '. prio=d  os_prio=d ' | grep -v -E 'locked <' | awk '{if ($0==""){print $0}else{printf"%s;",$0}}' | sort | uniq -c | awk '{a=$1;$1="";print $0,a}'

| 小結(jié)

火焰圖總結(jié)完了,以后再遇到性能問(wèn)題又多了一種應(yīng)對(duì)方式。


做開(kāi)發(fā)越久,越能感受得到工具的重要性,所以我準(zhǔn)備加一個(gè)專(zhuān)題來(lái)專(zhuān)門(mén)介紹我使用的各種工具。當(dāng)然,這也就更需要我更多地了解、使用和總結(jié)新的工具了。



本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專(zhuān)欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
關(guān)閉
關(guān)閉