性能調優利器:火焰圖

本文主要分享火焰圖使用技巧,介紹 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 火焰圖,所以這裏僅僅對這三種火焰圖作比較,也歡迎大家補充和斧正。

火焰圖類型 橫軸含義 縱軸含義 解決問題 採樣方式
cpu 火焰圖 cpu佔用時間 調用棧 找出 cpu 佔用高的問題函數;分析代碼熱路徑 固定頻率採樣 cpu 調用棧
off-cpu 火焰圖 阻塞時間 調用棧 i/o、網絡等阻塞場景導致的性能下降;鎖競爭、死鎖導致的性能下降問題 固定頻率採樣 阻塞事件調用棧
內存火焰圖 內存申請/釋放函數調用次數 調用棧 內存泄露問題;內存佔用高的對象/申請內存多的函數;虛擬內存或物理內存泄露問題 有四種方式: 跟蹤malloc/free;跟蹤brk;跟蹤mmap;跟蹤頁錯誤
Hot/Cold 火焰圖 on-CPU 火焰圖和 off-CPU 火焰圖結合在一起綜合展示 調用棧 需要結合 cpu 佔用以及阻塞分析的場景;off-CPU 火焰圖無法直觀判斷問題的場景 on-CPU 火焰圖和 off-CPU 火焰圖結合

火焰圖分析技巧

  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 內核模塊。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章