1. 程式人生 > >Linux下用火焰圖進行效能分析

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

1 火焰圖簡介

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

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

毫無疑問, 調優程式效能問題的時候, 同樣需要對症下藥. 好訊息是 Brendan D. Gregg 發明了火焰圖

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. Gregg 的 Flame Graph 工程實現了一套生成火焰圖的指令碼.

Flame Graph 專案位於 GitHub上

https://github.com/brendangregg/FlameGraph

用 git 將其 clone下來

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

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

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

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

2 用 perf 生成火焰圖

2.1 perf 採集資料

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

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

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 命令可以統計每個呼叫棧出現的百分比, 然後從高到低排列.

sudo perf report -n --stdio

2.2 生成火焰圖

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

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

將解析出來的資訊存下來, 供生成火焰圖 首先用 stackcollapse-perf.pl 將 perf 解析出的內容 perf.unfold 中的符號進行摺疊 :

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

最後生成 svg 圖

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

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

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

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 紅藍分叉火焰圖

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

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

4.1 紅藍差分火焰圖示例

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

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

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

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

在第二個 profile 中 deflate_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

一段時間後 (或者程式程式碼修改後), 抓取 profile 2` 檔案

#   抓取資料
perf record -F 99 -a -g -- sleep 30
#   解析資料生成堆疊資訊
perf script > out.stacks2
#   摺疊堆疊
./stackcollapse-perf.pl ../out.stacks2 > out.folded2