Linux性能優化實戰學習筆記:第十七講


問題 1: 使用 perf 工具時,看到的是 16 進制地址而不是函數名

1、分析過程

在 CentOS 系統中,使用 perf 工具看不到函數名,只能看到一些 16 進制格式的函數地址。

其實,只要你觀察一下 perf 界面最下面的那一行,就會發現一個警告信息Failed to open /opt/bitnami/php/lib/php/extensions/opcache.so, continuing without symbol

這說明,perf 找不到待分析進程依賴的庫。當然,實際上這個案例中有很多依賴庫都找不到,只不過,perf 工具本身只在最后一行顯示警告信息

這個問題,其實也是在分析 Docker 容器應用時,我們經常碰到的一個問題,因為容器應用依賴的庫都在鏡像里面。

方案一:在容器外面構建相同路徑的依賴庫

這種方法從原理上可行,但是我並不推薦,一方面是因為找出這些依賴庫比較麻煩,更重要的是構建這些路徑,會污染容器主機的環境。

方案二:在容器內部運行 perf

不過,這需要容器運行在特權模式下,但實際的應用程序往只以普通容器的方式運行。所以,容器內部一般沒有權限執行 perf 分析。

比方說,如果你在普通容器內部運行 perf record,你將會看到下面這個錯誤提示:

perf_4.9 record -a -g
perf_event_open(..., PERF_FLAG_FD_CLOEXEC) failed with unexpected error 1 (Operation not permitted)
perf_event_open(..., 0) failed unexpectedly with error 1 (Operation not permitted)

當然,其實你還可以通過配置 /proc/sys/kernel/perf_event_paranoid 許非特權用戶執行 perf 事件分析。

方案三:指定符號路徑為容器文件系統的路徑

比如對於第 05 講的應用,你可以執行下面這個命令:

mkdir /tmp/foo
$ PID=$(docker inspect --format {{.State.Pid}} phpfpm)
$ bindfs /proc/$PID/root /tmp/foo
$ perf report --symfs /tmp/foo

# 使用完成后不要忘記解除綁定
$ umount /tmp/foo/

不過這里要注意,bindfs 這個工具需要你額外安裝。bindfs 的基本功能是實現目錄綁定(類似於mount --bind),這里需要你安裝的是1.13.10 版本(這也是它的最新發布版)。

如果你安裝的是舊版本,你可以到 GitHub上面下載源碼,然后編譯安裝。https://github.com/mpartel/bindfs

方案四,在容器外面把分析紀錄保存下來,再去容器里查看結果這樣,庫和符號的路徑也就都對了。

1、先運行 perf record -g -p < pid>,執行一會兒(比如 15 秒)后,按 Ctrl+C 停止。

2、然后,把生成的 perf.data 文件,拷貝到容器里面來分析

docker cp perf.data phpfpm:/tmp 
$ docker exec -i -t phpfpm bash

3、接下來,在容器的 bash 中繼續運行下面的命令,安裝perf 並使用 perf report 查看報告:

cd /tmp/ 
$ apt-get update && apt-get install -y linux-tools linux-perf procps
$ perf_4.9 report

首先是 perf 工具的版本問題。在最后一步中,我們運行的工具是容器內部安裝的版本 perf_4.9,而不是普通的perf 命令。這是因為, perf 命令實際上是一個

軟連接,會跟內核的版本進行匹配,但鏡像里安裝的 perf 版本跟虛擬機的內核版本有可能並不一致

另外,php-fpm 鏡像是基於 Debian 系統的所以安裝 perf 工具的命令,跟 Ubuntu 也並不完也並不完全一樣。比如, Ubuntu 上的安裝方法是下面這樣

apt-get install -y linux-tools-common linux-tools-generic linux-tools-$(uname -r))

而在 php-fpm 容器里,你應該執行下面的命令來安裝安裝 perf:

apt-get install -y linux-perf

當你按照前面這幾種方法操作后,你就可以在容器內部看到 sqrsqrt 的堆棧

事實上,拋開我們的案例來說,即使是在非容器化的應用中,你也可能會碰到這個問題。假如你的應用程序在編譯時

使用 strip 刪除了 ELF 二進制文件的符號表,那么你同樣也只能看到函數的地址。

問題 2:如何用 perf 工具分析 Java 程序

 

像是 Java 這種通過 JVM 來運行的應用程序,運行堆棧用的都是 JVM 內置的函數和堆棧管理。所以,從系統層面你只能看到 JVM 的函數堆棧,

perf_events 實際上已經支持了 JIT,但還需要一個 /tmp/perf-PID.map 文件,來進行符號翻譯。當然,開源項目 perf-map-agent 可以幫你生成這符號表

此外,為了生成全部調用棧,你還需要開啟 JDK 的選項-XX:+PreserveFramePointer。因為這里涉及到大量的 Java 知識,我就不再詳細展開了。如果你的應用剛好基於 Java ,那么你可以參考 NETFLIX 的技術博客鏈接為

https://medium.com/netflix-techblogjava-in-flames-e763b3d32166

問題 3:為什么 perf 的報告中,很多符號都不顯示調用棧

1、man perf-report 命令,找到 -g 參數的說明

-g, --call-graph=<print_type,threshold[,print_limit],order,sort_key[,branch],value> 
Display call chains using type, min percent threshold, print limit, call order, sort key, optional branch and value. Note that 
ordering is not fixed so any parameter can be given in an arbitrary order. One exception is the print_limit which should be 
preceded by threshold.

print_type can be either: 
- flat: single column, linear exposure of call chains. 
- graph: use a graph tree, displaying absolute overhead rates. (default) 
- fractal: like graph, but displays relative rates. Each branch of 
the tree is considered as a new profiled object. 
- folded: call chains are displayed in a line, separated by semicolons 
- none: disable call chain display.

threshold is a percentage value which specifies a minimum percent to be 
included in the output call graph. Default is 0.5 (%).

print_limit is only applied when stdio interface is used. It's to limit 
number of call graph entries in a single hist entry. Note that it needs 
to be given after threshold (but not necessarily consecutive). 
Default is 0 (unlimited).

order can be either: 
- callee: callee based call graph. 
- caller: inverted caller based call graph. 
Default is 'caller' when --children is used, otherwise 'callee'.

sort_key can be: 
- function: compare on functions (default) 
- address: compare on individual code addresses 
- srcline: compare on source filename and line number

branch can be: 
- branch: include last branch information in callgraph when available. 
Usually more convenient to use --branch-history for this.

value can be: 
- percent: diplay overhead percent (default) 
- period: display event period 
- count: display event count

通過這個說明可以看到,-g 選項等同於 --call-graph,它的參數是后面那些被逗號隔開的選項,意思分別是輸出類型、最小閾值、輸出限制、排序方法、排序關鍵詞分支以及值的類型。

我們可以看到,這里默認的參數是 graph,0.5,callcaller,function,percent,具體含義文檔中都有詳細講解,這里我就不再重復了。

2、調整閥值

perf report -g graph,0.3

3、swapper 為什么會特別高?

它只在系統初始化時創建 init 進程,之后它就成了一個最低優先級的空閑任務。也就是說,當 CPU 上沒有其他任務運行時,就會執行 swapper 。所以,你可以稱它為“空閑任務”。

展開它的調用棧,你會看到, swapper 時鍾事件都耗費在了 do_idle 上,也就是在執行空閑任務

 

因為在多任務系統中,次數多的事件,不一定就是性能瓶頸。所以,只觀察到一個大數值,並不能說明什么問題。具體有沒有瓶頸,還需要你觀測多個方面的多個標,

來交叉驗證。這也是我在套路篇中不斷強調的一點。

4、關於 Children 和 Self 的含義

Self 是最后一列的符號(可以理解為函數)本身所占比例;

Children 是這個符號調用的其他符號(可以理解為子函數,包括直接和間接調用)占用的比例之和。

perf 這種動態追蹤工具,會給系統帶來一定的性能損失。

vmstat、pidstat 這些直接讀取 proc 系統來獲取指標的工具,不會帶來性能損失。

問題 5:性能優化書籍和參考資料推薦

書籍:《性能之巔:洞悉系統、企業與雲計算》

網站:http://www.brendangregg.com/linuxperf.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM