程式設計師精進之路:效能調優利器--火焰圖

騰訊技術工程發表於2020-06-16

程式設計師精進之路:效能調優利器--火焰圖

作者:厲輝,騰訊 CSIG 後臺開發工程師

本文主要分享火焰圖使用技巧,介紹 systemtap 的原理機制,如何使用火焰圖快速定位效能問題原因,同時加深對 systemtap 的理解。

讓我們回想一下,曾經作為程式設計新手的我們是如何調優程式的?通常是在沒有資料的情況下依靠主觀臆斷來瞎蒙,稍微有些經驗的同學則會對差異程式碼進行二分或者逐段除錯。這種定位問題的方式不僅耗時耗力,而且還不具有通用性,當遇到其他類似的效能問題時,需要重複踩坑、填坑,那麼如何避免這種情況呢?

俗語有曰:兵慾善其事必先利其器,個人認為,程式設計師定位效能問題也需要一件“利器”。如同醫生給病人看病,需要依靠專業的醫學工具(比如 X 光片、聽診器等)進行診斷,最後依據醫學工具的檢驗結果快速精準的定位出病因所在。效能調優工具(比如 perf / gprof 等)之於效能調優就像 X 光之於病人一樣,它可以一針見血的指出程式的效能瓶頸。

但是常用的效能調優工具 perf 等,在呈現內容上只能單一的列出呼叫棧或者非層次化的時間分佈,不夠直觀。這裡我推薦大家配合使用火焰圖,它將 perf 等工具採集的資料呈現得更為直觀。

初識火焰圖

火焰圖(Flame Graph)是由 Linux 效能優化大師 Brendan Gregg 發明的,和所有其他的 profiling 方法不同的是,火焰圖以一個全域性的視野來看待時間分佈,它從底部往頂部,列出所有可能導致效能瓶頸的呼叫棧。

程式設計師精進之路:效能調優利器--火焰圖

火焰圖整個圖形看起來就像一個跳動的火焰,這就是它名字的由來。

火焰圖有以下特徵(這裡以 on-cpu 火焰圖為例):

  • 每一列代表一個呼叫棧,每一個格子代表一個函式

  • 縱軸展示了棧的深度,按照呼叫關係從下到上排列。最頂上格子代表取樣時,正在佔用 cpu 的函式。

  • 橫軸的意義是指:火焰圖將採集的多個呼叫棧資訊,通過按字母橫向排序的方式將眾多資訊聚合在一起。需要注意的是它並不代表時間。

  • 橫軸格子的寬度代表其在取樣中出現頻率,所以一個格子的寬度越大,說明它是瓶頸原因的可能性就越大。

  • 火焰圖格子的顏色是隨機的暖色調,方便區分各個呼叫資訊。

  • 其他的取樣方式也可以使用火焰圖, on-cpu 火焰圖橫軸是指 cpu 佔用時間,off-cpu 火焰圖橫軸則代表阻塞時間。

  • 取樣可以是單執行緒、多執行緒、多程式甚至是多 host,進階用法可以參考附錄進階閱讀。

火焰圖型別

常見的火焰圖型別有 On-CPU,Off-CPU,還有 Memory,Hot/Cold,Differential 等等。他們分別適合處理什麼樣的問題呢?

這裡筆者主要使用到的是 On-CPU、Off-CPU 以及 Memory 火焰圖,所以這裡僅僅對這三種火焰圖作比較,也歡迎大家補充和斧正。

程式設計師精進之路:效能調優利器--火焰圖

火焰圖分析技巧

  1. 縱軸代表呼叫棧的深度(棧楨數),用於表示函式間呼叫關係:下面的函式是上面函式的父函式。

  2. 橫軸代表呼叫頻次,一個格子的寬度越大,越說明其可能是瓶頸原因。

  3. 不同型別火焰圖適合優化的場景不同,比如 on-cpu 火焰圖適合分析 cpu 佔用高的問題函式,off-cpu 火焰圖適合解決阻塞和鎖搶佔問題。

  4. 無意義的事情:橫向先後順序是為了聚合,跟函式間依賴或呼叫關係無關;火焰圖各種顏色是為方便區分,本身不具有特殊含義

  5. 多練習:進行效能優化有意識的使用火焰圖的方式進行效能調優(如果時間充裕)

如何繪製火焰圖?

要生成火焰圖,必須要有一個順手的動態追蹤工具,如果作業系統是 Linux 的話,那麼通常通常是 perf 或者 systemtap 中的一種。其中 perf 相對更常用,多數 Linux 都包含了 perf 這個工具,可以直接使用;SystemTap 則功能更為強大,監控也更為靈活。網上關於如何使用 perf 繪製火焰圖的文章非常多而且豐富,所以本文將以 SystemTap 為例。

SystemTap 是動態追蹤工具,它通過探針機制,來採集核心或者應用程式的執行資訊,從而可以不用修改核心和應用程式的程式碼,就獲得豐富的資訊,幫你分析、定位想要排查的問題。SystemTap 定義了一種類似的 DSL 指令碼語言,方便使用者根據需要自由擴充套件。不過,不同於動態追蹤的鼻祖 DTrace ,SystemTap 並沒有常駐核心的執行時,它需要先把指令碼編譯為核心模組,然後再插入到核心中執行。這也導致 SystemTap 啟動比較緩慢,並且依賴於完整的除錯符號表。

使用 SystemTap 繪製火焰圖的主要流程如下:

  • 安裝 SystemTap 以及 作業系統符號除錯表

  • 根據自己所需繪製的火焰圖型別以及程式型別選擇合適的指令碼

  • 生成核心模組

  • 執行 SystemTap 或者執行生成的核心模組統計資料

  • 將統計資料轉換成火焰圖

本文演示步驟將會基於作業系統 Tlinux 2.2

安裝 SystemTap 以及 作業系統符號除錯表

使用 yum 工具安裝 systemtap:

yum install systemtap systemtap-runtime

由於 systemtap 工具依賴於完整的除錯符號表,而且生產環境不同機器的核心版本不同(雖然都是Tlinux 2.2版本,但是核心版本後面的小版本不一樣,可以通過 uname -a 命令檢視)所以我們還需要安裝 kernel-debuginfo 包、 kernel-devel 包 我這裡是安裝了這兩個依賴包

kernel-devel-3.10.107-1-tlinux2-0046.x86_64
kernel-debuginfo-3.10.107-1-tlinux2-0046.x86_64

根據自己所需繪製的火焰圖型別以及程式型別選擇合適的指令碼

使用 SystemTap 統計相關資料往往需要自己依照它的語法,編寫指令碼,具有一定門檻。幸運的是,github 上春哥(agentzh)開源了兩組他常用的 SystemTap 指令碼:openresty-systemtap-toolkit 和 stapxx,這兩個工具集能夠覆蓋大部分 C 程式、nginx 程式以及 Openresty 程式的效能問題場景。

我們這裡需要繪製 off-cpu 火焰圖,所以使用 sample-bt-off-cpu 指令碼即可

生成核心模組

現在我們有了統計指令碼,也安裝好了 systemtap,正常來說就可以使用了,但由於 systemtap 是通過生成核心模組的方式統計相關探針的統計資料,而 tlinux 要求所有執行的核心模組需要先到 tlinux 平臺簽名才可以執行,所以:

故需要先修改 off-cpu 指令碼,讓其先生成核心模組;之後對該核心模組作簽名;最後使用 systemtap 命令手工執行該指令碼,統計監控資料

Systemtap 執行流程如下:

程式設計師精進之路:效能調優利器--火焰圖
  • parse:分析指令碼語法

  • elaborate:展開指令碼 中定義的探針和連線預定義指令碼庫,分析核心和核心模組的除錯資訊

  • translate:.將指令碼編譯成c語言核心模組檔案放 在$HOME/xxx.c 快取起來,避免同一指令碼多次編譯

  • build:將c語言模組檔案編譯成.ko的核心模組,也快取起來。

  • 把模組交給staprun,staprun載入核心模組到核心空間,stapio連線核心模組和使用者空間,提供互動IO通道,採集資料。

所以我們這裡修改下 off-cpu 的 stap 指令碼,讓其只執行完第四階段,只生成一個核心模組

// 在 stap 命令後增加 -p4 引數,告訴systemtap,當前只需要執行到第四階段
open my $in, "|stap -p4 --skip-badvars --all-modules -x $pid -d '$exec_path' --ldd $d_so_args $stap_args -"
or die "Cannot run stap: $!\n";

修改好之後執行指令碼,會生成一個核心模組

// -p 8682 是需要監控的程式的程式號
// -t 30 是指會取樣30秒
./sample-bt-off-cpu -p 8692 -t 30

生成的核心模組名稱形如 stap_xxxxx.ko模組名稱 由於讀者並不需要關心核心模組簽名,故章節略過

執行核心模組統計資料

核心模組簽名完成後,便可以使用 staprun 命令手工執行相關核心模組了

命令:

// 注意:簽名指令碼會將生產的核心模組重新命名,需要將名字改回去……(指令碼bug)
staprun -x {程式號} {核心模組名} > demo.bt

值得注意的是,監控的程式要有一定負載 systemtap 才可以採集到相關資料,即在採集時,同時需要要有一定請求量(通常是自己構造請求,壓測程式)

將統計資料轉換成火焰圖

獲得了統計資料 demo.bt 後,便可以使用火焰圖工具繪製火焰圖了

下載 FlameGraph,連結:

https://github.com/brendangregg/FlameGraph

命令:

./stackcollapse-stap.pl demo.bt > demo.folded
./flamegraph.pl demo.folded > demo.svg

這樣便獲得了 off-cpu 火焰圖:

程式設計師精進之路:效能調優利器--火焰圖

看圖說話

趁熱打鐵,通過幾張火焰圖熟悉下如何使用火焰圖

圖片來自於春哥微博或者個人近期定位的問題

on-cpu 火焰圖

Apache APISIX QPS急劇下降問題
程式設計師精進之路:效能調優利器--火焰圖

Apache APISIX 是一個開源國產的高效能 API 閘道器,之前在進行選型壓測時,發現當 Route 匹配不中場景下, QPS 急劇下降,在其 CPU (四十八核)佔用率幾乎達到100%的情況下只有幾千 QPS,通過繪製火焰圖發現,其主要耗時在一個 table 插入階段(lj_cf_table_insert),分析程式碼發現是該 table 一直沒有釋放,每次匹配不中路由會插入資料,導致表越來越大,後續插入耗時過長導致 QPS 下降。

off-cpu 火焰圖

nginx 互斥鎖問題
程式設計師精進之路:效能調優利器--火焰圖

這是一張 nginx 的 off-cpu 火焰圖,我們可以很快鎖定到 ngx_common_set_cache_fs_size -> ngx_shmtx_lock -> sem_wait 這段邏輯使用到了互斥鎖,它讓 nginx 程式絕大部分阻塞等待時間花費在獲取該鎖。

agent 監控上報斷點問題
程式設計師精進之路:效能調優利器--火焰圖

這是一張 agent 的 off-cpu 火焰圖,它是一個多執行緒非同步事件模型,主執行緒處理各個訊息,多個執行緒分別負責配置下發或者監控上報的職責。當前問題出現在監控上報效能差,無法在週期(一分鐘)內完成監控資料上報,導致監控斷點,通過 off-cpu 火焰圖我們可以分析出,該上報執行緒花費了大量的時間使用 curl_easy_perform 介面收發 http 監控資料訊息中。

依據火焰圖將傳送 http 訊息的邏輯改為非同步非阻塞後,該問題解決。

附錄

進階閱讀

FAQ

使用 perf 或者 systemtap 的方式採集資料,會對後臺服務有效能影響嗎?

有,但是很小,可以基本忽略不計。

它們使用系統的探針或者使用一些自定義的動態探針進行資料採集,第一對程式碼無侵入性,它既不需要停止服務,也不需要修改應用程式的程式碼;第二,它們是以核心模組/核心原生的方式跟蹤使用者態和核心態的所有事件,並通過一系列優化措施,進行取樣統計,對目標服務效能影響極小,大概在5%左右或者更低的效能損耗。相較於將程式執行在沙箱的 valgrind 工具或靜態除錯工具 gdb 來說,動態追蹤 perf 或者 systemtap 或者 ebpf 的效能損耗基本可以忽略不計。

目標程式重啟後,systemtap 是否需要重新生成核心模組?

不需要。甚至同一個 linux 核心版本下的同一個二進位制程式(md5值一致),在安裝 kernel 除錯符號表後,便可以在生成採集指標的核心模組,並且可以多次使用。

當 linux 核心版本不一致,符號表有變化,需要重新生成核心模組;當目標程式二進位制檔案重新編譯後,也需要重新生成統計用的 systemtap 核心模組。

程式設計師精進之路:效能調優利器--火焰圖

相關文章