在 CentOS 系統中,使用 perf 工具看不到函數名,只能看到一些 16 進制格式的函數地址。php
其實,只要你觀察一下 perf 界面最下面的那一行,就會發現一個警告信息Failed to open /opt/bitnami/php/lib/php/extensions/opcache.so, continuing without symbolhtml
這說明,perf 找不到待分析進程依賴的庫。固然,實際上這個案例中有不少依賴庫都找不到,只不過,perf 工具自己只在最後一行顯示警告信息java
這個問題,其實也是在分析 Docker 容器應用時,咱們常常碰到的一個問題,由於容器應用依賴的庫都在鏡像裏面。linux
這種方法從原理上可行,可是我並不推薦,一方面是由於找出這些依賴庫比較麻煩,更重要的是構建這些路徑,會污染容器主機的環境。git
不過,這須要容器運行在特權模式下,但實際的應用程序往只以普通容器的方式運行。因此,容器內部通常沒有權限執行 perf 分析。github
比方說,若是你在普通容器內部運行 perf record,你將會看到下面這個錯誤提示:docker
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 講的應用,你能夠執行下面這個命令:bash
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 版本(這也是它的最新發布版)。app
若是你安裝的是舊版本,你能夠到 GitHub上面下載源碼,而後編譯安裝。https://github.com/mpartel/bindfs
一、先運行 perf record -g -p < pid>,執行一下子(好比 15 秒)後,按 Ctrl+C 中止。
二、而後,把生成的 perf.data 文件,拷貝到容器裏面來分析
docker cp perf.data phpfpm:/tmp $ docker exec -i -t phpfpm bash
三、接下來,在容器的 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 二進制文件的符號表,那麼你一樣也只能看到函數的地址。
像是 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
-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,具體含義文檔中都有詳細講解,這裏我就再也不重複了。
perf report -g graph,0.3
它只在系統初始化時建立 init 進程,以後它就成了一個最低優先級的空閒任務。也就是說,當 CPU 上沒有其餘任務運行時,就會執行 swapper 。因此,你能夠稱它爲「空閒任務」。
展開它的調用棧,你會看到, swapper 時鐘事件都耗費在了 do_idle 上,也就是在執行空閒任務。
由於在多任務系統中,次數多的事件,不必定就是性能瓶頸。因此,只觀察到一個大數值,並不能說明什麼問題。具體有沒有瓶頸,還須要你觀測多個方面的多個標,
來交叉驗證。這也是我在套路篇中不斷強調的一點。
Self 是最後一列的符號(能夠理解爲函數)自己所佔比例;
Children 是這個符號調用的其餘符號(能夠理解爲子函數,包括直接和間接調用)佔用的比例之和。
perf 這種動態追蹤工具,會給系統帶來必定的性能損失。
vmstat、pidstat 這些直接讀取 proc 系統來獲取指標的工具,不會帶來性能損失。
書籍:《性能之巔:洞悉系統、企業與雲計算》
網站:http://www.brendangregg.com/linuxperf.html