1. 程式人生 > 實用技巧 >Linux下用火焰圖進行效能分析【轉】

Linux下用火焰圖進行效能分析【轉】

轉自:https://blog.csdn.net/gatieme/article/details/78885908

本作品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可, 轉載請註明出處, 謝謝合作

因本人技術水平和知識面有限, 內容如有紕漏或者需要修正的地方, 歡迎大家指正, 也歡迎大家提供一些其他好的除錯工具以供收錄, 鄙人在此謝謝啦

軟體的效能分析, 往往需要檢視 CPU 耗時, 瞭解瓶頸在哪裡.

火焰圖(flame graph) 是效能分析的利器

1 火焰圖簡介


很多人感冒發燒的時候, 往往會模仿神農氏嘗百草的路子: 先嚐嘗抗病毒的藥, 再試試抗細菌的藥, 甭管家裡有什麼藥挨個試, 什麼中藥西藥, 瞎貓總會碰上死耗子, 如此做法自然是不可取的, 正確的做法應該是去醫院驗個血, 確診後再對症下藥.

讓我們回想一下我們一般是如何除錯程式的 : 通常是在沒有資料的情況下依靠主觀臆斷來瞎蒙, 而不是考慮問題到底是什麼引起的!

毫無疑問, 調優程式效能問題的時候, 同樣需要對症下藥. 好訊息是 [Brendan D. Gregg]((http://www.brendangregg.com/perf.html#FlameGraphs) 發明了火焰圖

1.1 火焰圖


常見的火焰圖型別有 On-CPU, Off-CPU, 還有 Memory, Hot/Cold, Differential 等等.

關於火焰圖詳細的介紹可以參考 Blazing Performance with Flame Graphs, 簡而言之 : 整個圖形看起來就像一團跳動的火焰, 這也正是其名字的由來. 燃燒在火苗尖部的就是 CPU

正在執行的操作, 不過需要說明的是顏色是隨機的, 本身並沒有特殊的含義, 縱向表示呼叫棧的深度, 橫向表示消耗的時間. 因為呼叫棧在橫向會按照字母排序, 並且同樣的呼叫棧會做合併, 所以一個格子的寬度越大越說明其可能是瓶頸. 綜上所述, 主要就是看那些比較寬大的火苗, 特別留意那些類似平頂山的火苗.

要生成火焰圖, 必須要有一個順手的 Tracer 工具, 如果作業系統是 Linux 的話, 那麼選擇通常是 perf, systemtap 中的一種. 其中 perf 相對更常用, 因為它時 Linux Kernel 內建的效能調優工具, 多數 Linux 都包含了它, 有興趣的讀者稍後可以參考

Linux Profiling at Netflix 中的介紹, 尤其是裡面關於如何處理 Broken stacks 問題的描述, 建議多看幾遍, 而 systemtap 相對更強大, 不過缺點是你需要先學會它本身的程式語言.

早期火焰圖在 Nginx 和 社群比較活躍, 如果你是一個 Nginx 開發或者優化人員, 那麼我強烈推薦你使用 春哥nginx-systemtap-toolkit, 乍一看名字你可能會誤以為這個工具包是 nginx 專用的, 實際上這裡面很多工具適用於任何 C/CPP 語言編寫的程式:

程式功能
sample-bt 用來生成 On-CPU 火焰圖的取樣資料(DEMO)
sample-bt-off-cpu 用來生成 Off-CPU 火焰圖的取樣資料 (DEMO)

1.2 On/Off-CPU 火焰圖


那麼什麼時候使用 On-CPU 火焰圖? 什麼時候使用 Off-CPU 火焰圖呢?

取決於當前的瓶頸到底是什麼, 如果是 CPU 則使用 On-CPU 火焰圖, 如果是 IO 或鎖則使用 Off-CPU 火焰圖. 如果無法確定, 那麼可以通過壓測工具來確認 : 通過壓測工具看看能否讓 CPU 使用率趨於飽和, 如果能那麼使用 On-CPU 火焰圖, 如果不管怎麼壓, CPU 使用率始終上不來, 那麼多半說明程式被 IO 或鎖卡住了, 此時適合使用 Off-CPU 火焰圖.

如果還是確認不了, 那麼不妨 On-CPU 火焰圖和 Off-CPU 火焰圖都搞搞, 正常情況下它們的差異會比較大, 如果兩張火焰圖長得差不多, 那麼通常認為 CPU 被其它程序搶佔了.

在取樣資料的時候, 最好通過壓測工具對程式持續施壓, 以便採集到足夠的樣本. 關於壓測工具的選擇, 如果選擇 ab 的話, 那麼務必記得開啟 -k 選項, 以避免耗盡系統的可用埠. 此外, 我建議嘗試使用諸如 wrk 之類更現代的壓測工具.

1.3 火焰圖視覺化生成器


Brendan D. GreggFlame Graph 工程實現了一套生成火焰圖的指令碼.

Flame Graph 專案位於 GitHub

https://github.com/brendangregg/FlameGraph

git 將其 clone下來

git clone https://github.com/brendangregg/FlameGraph.git
  • 1

生成和建立火焰圖需要如下幾個步驟

流程描述指令碼
捕獲堆疊 使用 perf/systemtap/dtrace 等工具抓取程式的執行堆疊 perf/systemtap/dtrace
摺疊堆疊 trace 工具抓取的系統和程式執行每一時刻的堆疊資訊, 需要對他們進行分析組合, 將重複的堆疊累計在一起, 從而體現出負載和關鍵路徑 FlameGraph 中的 stackcollapse 程式
生成火焰圖 分析 stackcollapse 輸出的堆疊資訊生成火焰圖 flamegraph.pl

不同的 trace 工具抓取到的資訊不同, 因此 Flame Graph 提供了一系列的 stackcollapse 工具.

stackcollapse描述
stackcollapse.pl for DTrace stacks
stackcollapse-perf.pl for Linux perf_events “perf script” output
stackcollapse-pmc.pl for FreeBSD pmcstat -G stacks
stackcollapse-stap.pl for SystemTap stacks
stackcollapse-instruments.pl for XCode Instruments
stackcollapse-vtune.pl for Intel VTune profiles
stackcollapse-ljp.awk for Lightweight Java Profiler
stackcollapse-jstack.pl for Java jstack(1) output
stackcollapse-gdb.pl for gdb(1) stacks
stackcollapse-go.pl for Golang pprof stacks
stackcollapse-vsprof.pl for Microsoft Visual Studio profiles

2 用 perf 生成火焰圖


2.1 perf 採集資料


讓我們從 perf 命令(performance 的縮寫)講起, 它是 Linux 系統原生提供的效能分析工具, 會返回 CPU 正在執行的函式名以及呼叫棧(stack)

sudo perf record -F 99 -p 3887 -g -- sleep 30
  • 1

perf record 表示採集系統事件, 沒有使用 -e 指定採集事件, 則預設採集 cycles(即 CPU clock 週期), -F 99 表示每秒 99 次, -p 13204 是程序號, 即對哪個程序進行分析, -g 表示記錄呼叫棧, sleep 30 則是持續 30 秒.

-F 指定取樣頻率為 99Hz(每秒99次), 如果 99次 都返回同一個函式名, 那就說明 CPU 這一秒鐘都在執行同一個函式, 可能存在效能問題.

執行後會產生一個龐大的文字檔案. 如果一臺伺服器有 16CPU, 每秒抽樣 99 次, 持續 30 秒, 就得到 47,520 個呼叫棧, 長達幾十萬甚至上百萬行.

為了便於閱讀, perf record 命令可以統計每個呼叫棧出現的百分比, 然後從高到低排列.

sudo perf report -n --stdio
  • 1

2.2 生成火焰圖


首先用 perf script 工具對 perf.data 進行解析

# 生成摺疊後的呼叫棧
perf script -i perf.data &> perf.unfold
  • 1
  • 2

將解析出來的資訊存下來, 供生成火焰圖

首先用 stackcollapse-perf.pl 將 perf 解析出的內容 perf.unfold 中的符號進行摺疊 :

# 生成火焰圖
./stackcollapse-perf.pl perf.unfold &> perf.folded
  • 1
  • 2

最後生成 svg

./flamegraph.pl perf.folded > perf.svg
  • 1

我們可以使用管道將上面的流程簡化為一條命令

perf script | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > process.svg
  • 1

3 解析火焰圖


最後就可以用瀏覽器開啟火焰圖進行分析啦.

3.1 火焰圖的含義


火焰圖是基於 stack 資訊生成的 SVG 圖片, 用來展示 CPU 的呼叫棧。

y 軸表示呼叫棧, 每一層都是一個函式. 呼叫棧越深, 火焰就越高, 頂部就是正在執行的函式, 下方都是它的父函式.

x 軸表示抽樣數, 如果一個函式在 x 軸佔據的寬度越寬, 就表示它被抽到的次數多, 即執行的時間長. 注意, x 軸不代表時間, 而是所有的呼叫棧合併後, 按字母順序排列的.

火焰圖就是看頂層的哪個函式佔據的寬度最大. 只要有 “平頂”(plateaus), 就表示該函式可能存在效能問題。

顏色沒有特殊含義, 因為火焰圖表示的是 CPU 的繁忙程度, 所以一般選擇暖色調.

3.2 互動性


火焰圖是 SVG 圖片, 可以與使用者互動.

  • 滑鼠懸浮

火焰的每一層都會標註函式名, 滑鼠懸浮時會顯示完整的函式名、抽樣抽中的次數、佔據總抽樣次數的百分比

  • 點選放大

在某一層點選,火焰圖會水平放大,該層會佔據所有寬度,顯示詳細資訊。

左上角會同時顯示 “Reset Zoom”, 點選該連結, 圖片就會恢復原樣.

  • 搜尋

按下 Ctrl + F 會顯示一個搜尋框,使用者可以輸入關鍵詞或正則表示式,所有符合條件的函式名會高亮顯示.

3.3 侷限


兩種情況下, 無法畫出火焰圖, 需要修正系統行為.

  • 呼叫棧不完整

當呼叫棧過深時,某些系統只返回前面的一部分(比如前10層)。

  • 函式名缺失

有些函式沒有名字,編譯器只用記憶體地址來表示(比如匿名函式)。

3.4 瀏覽器的火焰圖


Chrome 瀏覽器可以生成頁面指令碼的火焰圖, 用來進行 CPU 分析.

開啟開發者工具, 切換到 Performance 面板. 然後, 點選”錄製” 按鈕, 開始記錄資料. 這時, 可以在頁面進行各種操作, 然後停止”錄製”.

這時, 開發者工具會顯示一個時間軸. 它的下方就是火焰圖.

瀏覽器的火焰圖與標準火焰圖有兩點差異 : 它是倒置的(即呼叫棧最頂端的函式在最下方); x 軸是時間軸, 而不是抽樣次數.

4 紅藍分叉火焰圖


參考 http://www.brendangregg.com/blog/2014-11-09/differential-flame-graphs.html

幸虧有了 CPU 火焰圖(flame graphs), CPU 使用率的問題一般都比較好定位. 但要處理效能回退問題, 就要在修改前後或者不同時期和場景下的火焰圖之間, 不斷切換對比, 來找出問題所在, 這感覺就是像在太陽系中搜尋冥王星. 雖然, 這種方法可以解決問題, 但我覺得應該會有更好的辦法.

所以, 下面就隆重介紹 紅/藍差分火焰圖(red/blue differential flame graphs)

4.1 紅藍差分火焰圖示例


上面是一副互動式 SVG 格式圖片. 圖中使用了兩種顏色來表示狀態, 紅色表示增長, 藍色表示衰減.

這張火焰圖中各火焰的形狀和大小都是和第二次抓取的 profile 檔案對應的 CPU 火焰圖是相同的. (其中, y 軸表示棧的深度, x 軸表示樣本的總數, 棧幀的寬度表示了 profile 檔案中該函數出現的比例, 最頂層表示正在執行的函式, 再往下就是呼叫它的棧).

在下面這個案例展示了, 在系統升級後, 一個工作載荷的 CPU 使用率上升了. 下面是對應的 CPU 火焰圖(SVG 格式)

通常, 在標準的火焰圖中棧幀和棧塔的顏色是隨機選擇的. 而在紅/藍差分火焰圖中, 使用不同的顏色來表示兩個 profile 檔案中的差異部分.

在第二個 profiledeflate_slow( ) 函式以及它後續呼叫的函式執行的次數要比前一次更多, 所以在上圖中這個棧幀被標為了紅色. 可以看出問題的原因是ZFS的壓縮功能被啟用了, 而在系統升級前這項功能是關閉的.

這個例子過於簡單, 我甚至可以不用差分火焰圖也能分析出來. 但想象一下, 如果是在分析一個微小的效能下降, 比如說小於5%, 而且程式碼也更加複雜的時候, 問題就為那麼好處理了.

4.2 紅藍差分火焰圖簡介


這個事情我已經討論了好幾年了, 最終我自己編寫了一個我個人認為有價值的實現。它的工作原理是這樣的 :

  1. 抓取修改前的堆疊 profile1 檔案

  2. 抓取修改後的堆疊 profile2 檔案

  3. 使用 profile2 來生成火焰圖. (這樣棧幀的寬度就是以profile2 檔案為基準的)

  4. 使用 “2-1” 的差異來對火焰圖重新上色. 上色的原則是, 如果棧幀在 profile2 中出現出現的次數更多, 則標為紅色, 否則標為藍色. 色彩是根據修改前後的差異來填充的.

這樣做的目的是, 同時使用了修改前後的 profile 檔案進行對比, 在進行功能驗證測試或者評估程式碼修改對效能的影響時,會非常有用. 新的火焰圖是基於修改後的 profile 檔案生成(所以棧幀的寬度仍然顯示了當前的CPU消耗). 通過顏色的對比,就可以瞭解到系統性能差異的原因。

只有對效能產生直接影響的函式才會標註顏色(比如說,正在執行的函式),它所呼叫的子函式不會重複標註。

4.3 生成紅/藍差分火焰圖


作者的 GitHub 倉庫 FlameGrdph 中實現了一個程式指令碼,difffolded.pl 用來生成紅藍差分火焰圖. 為了展示工具是如何工作的, 用 Linux perf_events 來演示一下操作步驟. 你也可以使用其他 profiler/tracer.

  • 抓取修改前的profile 1檔案:
#   抓取資料
perf record -F 99 -a -g -- sleep 30
#   解析資料生成堆疊資訊
perf script > out.stacks1
#   摺疊堆疊
./stackcollapse-perf.pl ../out.stacks1 > out.folded1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 一段時間後 (或者程式程式碼修改後), 抓取 profile 2` 檔案
#   抓取資料
perf record -F 99 -a -g -- sleep 30
#   解析資料生成堆疊資訊
perf script > out.stacks2
#   摺疊堆疊
./stackcollapse-perf.pl ../out.stacks2 > out.folded2
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

生成紅藍差分火焰圖

./difffolded.pl out.folded1 out.folded2 | ./flamegraph.pl > diff2.svg
  • 1

difffolded.pl 只能對 “摺疊” 過的堆疊 profile 檔案進行操作, 摺疊操作 是由前面的 stackcollapse 系列指令碼完成的. 指令碼共輸出 3 列資料, 其中一列代表摺疊的呼叫棧, 另兩列為修改前後 profile 檔案的統計資料.

func_a;func_b;func_c 31 33
[...]
  • 1
  • 2

在上面的例子中 “funca()->funcb()->func_c()” 代表呼叫棧,這個呼叫棧在 profile1檔案中共出現了31次, 在profile2檔案中共出現了33次. 然後, 使用flamegraph.pl指令碼處理這3` 列資料, 會自動生成一張紅/藍差分火焰圖.

再介紹一些有用的選項:

其他選項描述
difffolded.pl -n 這個選項會把兩個profile檔案中的資料規範化,使其能相互匹配上。如果你不這樣做,抓取到所有棧的統計值肯定會不相同,因為抓取的時間和CPU負載都不同。這樣的話,看上去要麼就是一片紅(負載增加),要麼就是一片藍(負載下降)。-n選項對第一個profile檔案進行了平衡,這樣你就可以得到完整紅/藍圖譜
difffolded.pl -x 這個選項會把16進位制的地址刪掉。 profiler時常會無法將地址轉換為符號,這樣的話棧裡就會有16進位制地址。如果這個地址在兩個profile檔案中不同,這兩個棧就會認為是不同的棧,而實際上它們是相同的。遇到這樣的問題就用-x選項搞定
flamegraph.pl –negate 用於顛倒紅/藍配色。 在下面的章節中,會用到這個功能

4.4 不足之處


雖然紅/藍差分火焰圖很有用, 但實際上還是有一個問題 : 如果一個程式碼執行路徑完全消失了, 那麼在火焰圖中就找不到地方來標註藍色. 你只能看到當前的 CPU 使用情況, 而不知道為什麼會變成這樣.

一個辦法是, 將對比順序顛倒, 畫一個相反的差分火焰圖. 例如 :

上面的火焰圖是以修改前的 profile 檔案為基準, 顏色表達了將要發生的情況. 右邊使用藍色高亮顯示的部分, 從中可以看出修改後 CPU Idle 消耗的 CPU 時間會變少. (其實, 通常會把 cpuidle 給過濾掉, 使用命令列 grep -v cpuidle)

圖中把消失的程式碼也突顯了出來(或者應該是說, 沒有突顯), 因為修改前並沒有使能壓縮功能, 所以它沒有出現在修改前的 profile 檔案了, 也就沒有了被表為紅色的部分.

下面是對應的命令列:

./difffolded.pl out.folded2 out.folded1 | ./flamegraph.pl --negate > diff1.svg
  • 1

這樣, 把前面生成 diff2.svg 一併使用,我們就能得到:

火焰圖資訊描述
diff1.svg 寬度是以修改前profile檔案為基準,顏色表明將要發生的情況
diff2.svg 寬度是以修改後profile檔案為基準,顏色表明已經發生的情況

如果是在做功能驗證測試,我會同時生成這兩張圖。

4.5 CPI 火焰圖


這些指令碼開始是被使用在CPI火焰圖 的分析上. 與比較修改前後的 profile 檔案不同, 在分析 CPI 火焰圖時, 可以分析 CPU 工作週期與停頓週期的差異變化, 這樣可以凸顯出CPU的工作狀態來.

4.6 其他的差分火焰圖


也有其他人做過類似的工作. Robert Mustacchi 在不久前也做了一些嘗試,他使用的方法類似於程式碼檢視時的標色風格:只顯示了差異的部分,紅色表示新增(上升)的程式碼路徑,藍色表示刪除(下降)的程式碼路徑。一個關鍵的差別是棧幀的寬度只體現了差異的樣本數。右邊是一個例子。這個是個很好的主意,但在實際使用中會感覺有點奇怪,因為缺失了完整profile檔案的上下文作為背景,這張圖顯得有些難以理解。

Cor-Paul Bezemer也製作了一種差分顯示方法flamegraphdiff, 他同時將3張火焰圖放在同一張圖中,修改前後的標準火焰圖各一張,下面再補充了一張差分火焰圖,但棧幀寬度也是差異的樣本數。 上圖是一個例子. 在差分圖中將滑鼠移到棧幀上,3張圖中同一棧幀都會被高亮顯示。這種方法中補充了兩張標準的火焰圖,因此解決了上下文的問題。

我們3人的差分火焰圖,都各有所長。三者可以結合起來使用:Cor-Paul方法中上方的兩張圖,可以用我的diff1.svg 和 diff2.svg。下方的火焰圖可以用Robert的方式。為保持一致性,下方的火焰圖可以用我的著色方式:藍->白->紅。

火焰圖正在廣泛傳播中,現在很多公司都在使用它。如果大家知道有其他的實現差分火焰圖的方式,我也不會感到驚訝。(請在評論中告訴我)

4.7 總結


如果你遇到了效能回退問題,紅/藍差分火焰圖是找到根因的最快方式。這種方式抓取了兩張普通的火焰圖,然後進行對比,並對差異部分進行標色:紅色表示上升,藍色表示下降。 差分火焰圖是以當前(“修改後”)的profile檔案作為基準,形狀和大小都保持不變。因此你通過色彩的差異就能夠很直觀的找到差異部分,且可以看出為什麼會有這樣的差異。

差分火焰圖可以應用到專案的每日構建中,這樣效能回退的問題就可以及時地被發現和修正。

via: http://www.brendangregg.com/blog/2014-11-09/differential-flame-graphs.html

5 參考


使用linux perf工具生成java程式火焰圖

使用perf生成Flame Graph(火焰圖)

大神brendangregg的站點

CSDNGitHub
Linux下用火焰圖進行效能分析 LDD-LinuxDeviceDrivers/study/debug/tools/perf/flame_graph