之前有分享過自己工作中自己搭建的CPU監(jiān)控腳本等,但那個屬于是自己手工寫的一些腳本,比較粗淺的使用。后來就直接使用perf編譯到驅(qū)動里面,在設(shè)備中直接使用perf了,比起自己寫的腳本,效率直線提升。今天就來分享以下perf的功能使用,它可以將消耗 CPU 時間比較大的用戶程序調(diào)用棧打印出來,并生成火焰圖。
perf的介紹和安裝
Perf 是Linux kernel自帶的系統(tǒng)性能優(yōu)化工具。Perf的優(yōu)勢在于與Linux Kernel的緊密結(jié)合,它可以最先應(yīng)用到加入Kernel的new feature。pef可以用于查看熱點函數(shù),查看cashe miss的比率,從而幫助開發(fā)者來優(yōu)化程序性能,也可以分析程序運行期間發(fā)生的硬件事件,比如 instructions retired ,processor clock cycles 等;您也可以分析
軟件事件,比如 Page Fault 和進程切換,這使得 Perf 擁有了眾多的性能分析能力,
通過它,應(yīng)用程序可以利用 PMU,tracepoint 和內(nèi)核中的特殊計數(shù)器來進行性能統(tǒng)計。它不但可以分析指定應(yīng)用程序的性能問題 (per thread),也可以用來分析內(nèi)核的性能問題,當然也可以同時分析應(yīng)用代碼和內(nèi)核,從而全面理解應(yīng)用程序中的性能瓶頸。
舉例來說,使用 Perf 可以計算每個時鐘周期內(nèi)的指令數(shù),稱為 IPC,IPC 偏低表明代碼沒有很好地利用 CPU。Perf 還可以對程序進行函數(shù)級別的采樣,從而了解程序的性能瓶頸究竟在哪里等等。Perf 還可以替代 strace,可以添加動態(tài)內(nèi)核 probe 點,還可以做 benchmark 衡量調(diào)度器的好壞。。。
ubuntu安裝:
sudo apt-get install linux-tools-common linux-tools-"$(uname -r)" linux-cloud-tools-"$(uname -r)" linux-tools-generic linux-cloud-tools-generic
安裝好之后使用perf -v
命令查看版本
在設(shè)備中安裝
如果你使用yocto,那么可是用bitbake perf 直接編譯perf工具出來,然后做成鏡像燒錄到設(shè)備中,如果你使用的是其他根文件系統(tǒng)制作工具,方法也是類似。
將編譯好的的lib和bin目錄拷貝到設(shè)備中使用。
perf基本使用
它和Oprofile性能調(diào)優(yōu)工具等的基本原理都是對被監(jiān)測對象進行采樣,最簡單的情形是根據(jù) tick 中斷進行采樣,即在 tick 中斷內(nèi)觸發(fā)采樣點,在采樣點里判斷程序當時的上下文。假如一個程序 90% 的時間都花費在函數(shù) foo() 上,那么 90% 的采樣點都應(yīng)該落在函數(shù) foo() 的上下文中。運氣不可捉摸,那么只要采樣頻率足夠高,采樣時間足夠長,那么以上推論就比較可靠。因此,通過 tick 觸發(fā)采樣,我們便可以了解程序中哪些地方最耗時間,從而重點分析。
上面介紹了perf的原理,“根據(jù) tick 中斷進行采樣,即在 tick 中斷內(nèi)觸發(fā)采樣點,在采樣點里判斷程序當時的上下文”,我們可以改變采樣的觸發(fā)條件使得我們可以獲得不同的統(tǒng)計數(shù)據(jù),例如 以時間點 ( 如 tick) 作為事件觸發(fā)采樣便可以獲知程序運行時間的分布;以 cache miss 事件觸發(fā)采樣便可以知道 cache miss 的分布,即 cache 失效經(jīng)常發(fā)生在哪些程序代碼中。如此等等。
首先我們可以看一下 perf 中能夠觸發(fā)采樣的事件有哪些。
perf list使用,可以列出所有的采樣事件
sudo perf list
可以看到 Hadrware event Software event等
Hardware Event 是由 PMU 硬件產(chǎn)生的事件,比如 cache 命中,當您需要了解程序?qū)τ布匦缘氖褂们闆r時,便需要對這些事件進行采樣
Software Event 是內(nèi)核軟件產(chǎn)生的事件,比如進程切換,tick 數(shù)等
Tracepoint event 是內(nèi)核中的靜態(tài) tracepoint 所觸發(fā)的事件,這些 tracepoint 用來判斷程序運行期間內(nèi)核的行為細節(jié),比如 slab 分配器的分配次數(shù)等
perf stat 概覽程序的運行情況
perf stat選項,可以在終端上執(zhí)行命令時收集性能統(tǒng)計信息
sudo perf stat -p 11664
指定進程查看,ctrl +c 殺死進程之后,就可以看到相應(yīng)的數(shù)據(jù)了。
task-clock(msec)是指程序運行期間占用了xx的任務(wù)時鐘周期,該值高,說明程序的多數(shù)時間花費在 CPU 計算上而非 IO
context-switches是指程序運行期間發(fā)生了xx次上下文切換,記錄了程序運行過程中發(fā)生了多少次進程切換,頻繁的進程切換是應(yīng)該避免的。(有進程進程間頻繁切換,或者內(nèi)核態(tài)與用戶態(tài)頻繁切換)
cpu-migrations 是指程序運行期間發(fā)生了xx次CPU遷移,即用戶程序原本在一個CPU上運行,后來遷移到另一個CPU
cycles:處理器時鐘,一條機器指令可能需要多個 cycles
Instructions: 機器指令數(shù)目。
其他可以監(jiān)控的譬如分支預(yù)測、cache命中,page-faults 是指程序發(fā)生了xx次頁錯誤等
sudo perf stat -p 13465
root@lyn:/mnt# ps -ux | grep target
root 13465 89.7 0.1 4588 1472 pts/1 R+ 17:30 0:07 ./target
root 13467 0.0 0.0 3164 744 pts/0 S+ 17:30 0:00 grep target
root@lyn:/mnt# perf stat -p 13465
^C
Performance counter stats for process id '13465':
13418.914783 task-clock (msec) # 1.000 CPUs utilized
13 context-switches # 0.001 K/sec
0 cpu-migrations # 0.000 K/sec
0 page-faults # 0.000 K/sec
25072130385 cycles # 1.868 GHz
20056061 stalled-cycles-frontend # 0.08% frontend cycles idle
8663621265 stalled-cycles-backend # 34.55% backend cycles idle
27108898221 instructions # 1.08 insns per cycle
# 0.32 stalled cycles per insn
3578980615 branches # 266.712 M/sec
841545 branch-misses # 0.02% of all branches
13.419173431 seconds time elapsed
參考?鏈接
perf top實時顯示當前系統(tǒng)的性能統(tǒng)計信息
sudo perf top -g
用于實時顯示當前系統(tǒng)的性能統(tǒng)計信息。該命令主要用來觀察整個系統(tǒng)當前的狀態(tài),比如可以通過查看該命令的輸出來查看當前系統(tǒng)最耗時的內(nèi)核函數(shù)或某個用戶進程。
[.] : user level 用戶態(tài)空間,若自己監(jiān)控的進程為用戶態(tài)進程,那么這些即主要為用戶態(tài)的cpu-clock占用的數(shù)值
[k]: kernel level 內(nèi)核態(tài)空間
[g]: guest kernel level (virtualization) 客戶內(nèi)核級別
[u]: guest os user space 操作系統(tǒng)用戶空間
[H]: hypervisor 管理程序
The final column shows the symbol name.
當 perf 收集調(diào)用鏈時,開銷可以在兩列中顯示為Children和Self 。這里的Self
列與沒有“-g”的列類似:這是每個函數(shù)花費的 CPU 周期百分比。但是Children
列在其下方添加了所有調(diào)用函數(shù)所花費的時間。不僅是直系子女,而且是所有后代。對于調(diào)用圖的葉子,函數(shù)不調(diào)用其他任何東西,Self 和 Children 的值是相等的。但是對于 main(),它增加了在 f1()<-main() 和 f2()<-main() 中花費的時間。您將第一行讀為:95.61% 的時間花在調(diào)用 main() 上,而只有 8.19% 的時間花在 main() 指令上,因為它大部分時間都在調(diào)用其他函數(shù)。請注意,您可以添加“Self”以覆蓋 100%,但在“Children”中,兒童樣本占多行。這個想法是在頂部查看占樣本最多的調(diào)用堆棧片段。
有一個“+”,可以向下查看調(diào)用關(guān)系。
perf record 記錄采集的數(shù)據(jù)
使用 top 和 stat 之后,perf可能已經(jīng)大致有數(shù)了。要進一步分析,便需要一些粒度更細的信息。比如說我們已經(jīng)斷
使用 top 和 stat 之后,perf可能已經(jīng)大致有數(shù)了。要進一步分析,便需要一些粒度更細的信息。比如說我們已經(jīng)斷定目標程序計算量較大,也許是因為有些代碼寫的不夠精簡。那么面對長長的代碼文件,究竟哪幾行代碼需要進一步修改呢?這便需要使用 perf record 記錄單個函數(shù)級別的統(tǒng)計信息,并使用 perf report 來顯示統(tǒng)計結(jié)果(perf record表示記錄到文件,perf top直接會顯示到界面)。
perf record
?,它可以對事件進行采樣,將采樣的數(shù)據(jù)收集在一個 perf.data 的文件中,這將會帶來一定的性能開銷,不過這個命令很有用,可以用來找出最占 CPU 的進程。
下面的命令對系統(tǒng) CPU 事件做采樣,采樣時間為 60 秒,每秒采樣 99 個事件,-g表示記錄程序的調(diào)用棧。
sudoperf record -F 99 -a -g -- sleep 60
此外我們還可以使用PID監(jiān)控程序perf record -e cpu-clock -g?-p pid
?監(jiān)控 已啟動的進程;也可以使用程序名監(jiān)控程序perf record -e cpu-clock -g -p grep your_program
-e
選項允許您在perf list命令中列出的多個類別中選擇一個事件類別。例如,在這里,我們使用-e cpu-clock?
是指perf record監(jiān)控的指標為cpu周期程序運行完之后,perf record會生成一個名為perf.data的文件(缺省值),如果之前已有,那么之前的perf.data文件會變?yōu)閜erf.data.old文件
-g
選項是告訴perf record額外記錄函數(shù)的調(diào)用關(guān)系,因為原本perf record記錄大都是庫函數(shù),直接看庫函數(shù),大多數(shù)情況下,你的代碼肯定沒有標準庫的性能好對吧?除非是針對產(chǎn)品進行特定優(yōu)化,所以就需要知道是哪些函數(shù)頻繁調(diào)用這些庫函數(shù),通過減少不必要的調(diào)用次數(shù)來提升性能
perf record的其他參數(shù):
-f:強制覆蓋產(chǎn)生的.data數(shù)據(jù)
-c:事件每發(fā)生count次采樣一次
-p:指定進程
-t:指定線程
可以使用ctrl+c中斷perf進程,或者在命令最后加上參數(shù)?--sleep n?
(n秒后停止)
sudo perf report -n可以生成報告的預(yù)覽。
sudo perf report -n --stdio可以生成一個詳細的報告。
sudo perf script可以 dump 出 perf.data 的內(nèi)容。
獲得這個perf.data文件之后,我們其實還不能直接查看,下面就需要perf report工具進行查看
perf report輸出 record的結(jié)果
如果record之后想直接輸出結(jié)果,使用perf report即可
perf report的相關(guān)參數(shù):
-i : 指定文件輸出
-k:指定未經(jīng)壓縮的內(nèi)核鏡像文件,從而獲得內(nèi)核相關(guān)信息
--report:cpu按照cpu列出負載
sudo perf report
Samples: 123K of event 'cycles', Event count (approx.): 36930701307
Overhead Command Shared Object Symbol
18.91% swapper [kernel.kallsyms] [k] intel_idle
5.18% dev_ui libQt5lxxx [.] 0x00000000013044c7
3.20% dev_ui libc-2.19.so [.] _int_malloc
1.03% dev_ui libc-2.19.so [.] __clock_gettime
3.04% todesk libpixman-1.so.0.38.4 [.] 0x000000000008cac0
1.20% todesk [JIT] tid 126593 [.] 0x0000000001307c7a
0.84% todesk [JIT] tid 126593 [.] 0x000000000143c3f4
0.73% Xorg i965_dri.so [.] 0x00000000007cefe0
0.65% todesk libsciter-gtk.so [.] tool::tslice<gool::argb>::xcopy
0.58% Xorg i965_dri.so [.] 0x00000000007cf00e
0.53% Xorg i965_dri.so [.] 0x00000000007cf03c
0.49% todesk [JIT] tid 126593 [.] 0x0000000001307cb2
0.48% Xorg i965_dri.so [.] 0x00000000007cf06a
0.44% todesk [JIT] tid 126593 [.] 0x0000000001307cb6
0.41% todesk [JIT] tid 126593 [.] 0x0000000001307cc0
0.40% x-terminal-emul libz.so.1.2.11 [.] adler32_z
0.40% todesk [JIT] tid 126593 [.] 0x0000000001307c83
0.38% todesk [JIT] tid 126593 [.] 0x0000000001307cbb
0.33% swapper [kernel.kallsyms] [k] menu_select
0.32% gnome-shell libmutter-clutter-6.so.0.0.0 [.] clutter_actor_paint
0.31% gnome-shell libgobject-2.0.so.0.6400.6 [.] g_type_check_instance_is_a
0.31% swapper [kernel.kallsyms] [k] psi_group_change
0.24% SDK_Timer-8 [kernel.kallsyms] [k] psi_group_change
0.24% todesk libc-2.31.so [.] __memset_avx2_unaligned_erms
0.18% todesk [JIT] tid 126593 [.] 0x00000000013044c7
0.18% todesk [JIT] tid 126593 [.] 0x000000000143c3f0
0.17% gnome-shell libglib-2.0.so.0.6400.6 [.] g_hash_table_lookup
0.17% todesk [JIT] tid 126593 [.] 0x000000000143c426
0.17% todesk [JIT] tid 126593 [.] 0x000000000143c3dd
0.16% todesk [JIT] tid 126593 [.] 0x000000000143c3d9
0.16% swapper [kernel.kallsyms] [k] cpuidle_enter_state
0.16% SDK_Timer-8 [kernel.kallsyms] [k] syscall_exit_to_user_mode
0.16% swapper [kernel.kallsyms] [k] __sched_text_start
在第二行我們發(fā)現(xiàn)一個dev_ui ,占用了5.18%,使用了libQt5lxxx庫, 它本身功能UI顯示,,但是占用較高的CPU,說明調(diào)用該庫存在問題(代碼本身問題),需要對調(diào)用該庫的代碼進行檢查。
第三行l(wèi)ibc-2.19.so [.] _int_malloc 這是常用的malloc操作,由于對代碼比較熟悉,在這個過程中不應(yīng)該有這么多次申請內(nèi)存,說明代碼本身有問題,需要對申請動態(tài)內(nèi)存的代碼進行檢查
第四行行 __clock_gettime 這個是由于計時需要,需要頻繁獲取時間,通常是指 gettimeofday()函數(shù)
整個統(tǒng)計顯示有很多task-clock占用是由于kernel.kallsyms導致,同時也驗證了對perf stat獲得的數(shù)據(jù)的初步判斷,即CPU飆升也與頻繁的CPU遷移(內(nèi)核態(tài)中斷用戶態(tài)操作)導致,解決辦法是采用CPU綁核
也許有的人會奇怪為什么自己完全是一個用戶態(tài)的程序為什么還會統(tǒng)計到內(nèi)核態(tài)的指標?一是用戶態(tài)程序運行時會受到內(nèi)核態(tài)的影響,若內(nèi)核態(tài)對用戶態(tài)影響較大,統(tǒng)計內(nèi)核態(tài)信息可以了解到是內(nèi)核中的哪些行為導致對用戶態(tài)產(chǎn)生影響;二則是有些用戶態(tài)程序也需要依賴內(nèi)核的某些操作,譬如I/O操作+ 4.93% dev_ui libcurl-gnutls.so.4.3.0 [.] 0x000000000001e1e0 ,左邊的加號代表perf已經(jīng)記錄了該調(diào)用關(guān)系,按enter鍵可以查看調(diào)用關(guān)系,perf監(jiān)控該進程結(jié)果記錄到很多內(nèi)核調(diào)用,說明該進程在運行過程中,有可能被內(nèi)核態(tài)任務(wù)頻繁中斷,應(yīng)盡量避免這種情況,對于這個問題我的解決辦法是采用綁核,譬如機器有8個CPU,那么我就綁定內(nèi)核操作、中斷等主要在0-5CPU,GW由于有兩個線程,分別綁定到6、7CPU上。
注意:調(diào)優(yōu)應(yīng)該將注意力集中到百分比高的熱點代碼片段上,假如一段代碼只占用整個程序運行時間的 0.1%,即使您將其優(yōu)化到僅剩一條機器指令,恐怕也只能將整體的程序性能提高 0.1%。俗話說,好鋼用在刀刃上.
參考文章:鏈接
也可以用關(guān)鍵詞篩選
使用了sudo perf report
?可以查看當前perf.data的數(shù)據(jù),但是當你代碼調(diào)用很多時候不好進行分析查看,這個時候我們就可以選擇我們需要關(guān)注的重點信息查看,提高效率。例如以下的futex_wait:
選中之后,使用?--call-graph ,,,,callee --symbol-filter =?
后面增加你需要篩選監(jiān)控的類型就可以單獨顯示了。
sudo perf report --call-graph ,callee –symbol-filter
=futex_wait
這篇文章可以更多的幫助你理解filter:鏈接
perf diff進行兩次record對比
我們多次perf record之后,當前路徑下會有兩個perf.data 和perf.data.old文件,分別是本次和上次record的記錄,這個時候我們可以通過perf diff進行對比優(yōu)化的結(jié)果。
sudo perf diff perf.data perf.data.old
介紹一些了perf細節(jié)使用的描繪,再給大家分享一個perf詳細使用介紹的網(wǎng)址,大家對于perf介紹中有需要繼續(xù)深入探索的部分,可以點擊以下鏈接進行學習。
perf Examples?詳細的使用介紹
鏈接:https://www.brendangregg.com/perf.html
Linux Perf commands命令介紹使用
鏈接:https://linuxhint.com/linux-perf-commands/
火焰圖的制作
CPU 的性能,它可以將消耗 CPU 時間比較大的用戶程序調(diào)用棧打印出來,并生成火焰圖。通過分析火焰圖的頂層的顯示,我們就可以很直觀的查看我們函數(shù)的性能情況了。
這個是自己ubuntu20系統(tǒng)做捕獲的火焰圖顯示
x軸表示采樣次數(shù)或者頻率,如果一個函數(shù)在 x 軸占據(jù)的寬度越寬,就表示它被抽到的次數(shù)多,即執(zhí)行的時間長。注意,x 軸不代表時間,而是所有的調(diào)用棧合并后,按字母順序排列的。
y 軸表示調(diào)用棧,每一層都是一個函數(shù)。調(diào)用棧越深,火焰就越高,頂部就是正在執(zhí)行的函數(shù),下方都是它的父函數(shù)。
火焰圖就是看頂層的哪個函數(shù)占據(jù)的寬度最大。只要有"平頂"(plateaus),就表示該函數(shù)可能存在性能問題。
參考:鏈接1;?鏈接2
具體步驟:
1 首先,在 Ubuntu 安裝 perf 工具:
2 再從github下載分析腳本
git clone https://github.com/brendangregg/FlameGraph.git
3 使用perf script工具對perf.data進行解析
perf script -i perf.data &> perf.unfold
生成火焰圖通常的做法是將 perf.unfold 拷貝到本地機器,在本地生成火焰圖
4 將perf.unfold中的符號進行折疊
FlameGraph/stackcollapse-perf.pl perf.unfold &> perf.folded
5 最后生成svg圖
FlameGraph/flamegraph.pl perf.folded > perf.svg
生成火焰圖可以指定參數(shù),–width 可以指定圖片寬度,–height 指定每一個調(diào)用棧的高度,生成的火焰圖,寬度越大就表示CPU耗時越多。
注?:如果svg圖出現(xiàn)unknown函數(shù),使用如下命令
sudo perf record -e cpu-clock?--call-graph dwarf?
-p pid
范例:perf record -e cpu-clock -g -p 29713 --call-graph dwarf?
使用–call-graph dwarf 之后record生成的perf.data很大,大家生成的時候要時刻注意設(shè)備剩余空間是否足夠
實際測試范例
如圖一段代碼
main -> do_main -> foo -> bar
其中 foo 函數(shù)和 bar 各有一個for循環(huán),用來表示代碼時間運行消耗的cpu
#include <iostream>
#include <vector>
#include <string>
#include <unistd.h>
using namespace std;
void bar(){
// usleep(40*1000);
/* do something here */
for(int i=0;i< 4000;i++)
{
}
}
void foo(){
// usleep(60*1000);
for(int i=0;i< 5700;i++)
{
}
bar();
}
void do_main() {
foo();
}
int main(int argc,char** argv){
while(1)
{
do_main();
}
}
運行代碼之后進行 top實時查看(因為我的設(shè)備默認都是sudo權(quán)限,所以以下命令都不用前綴sudo)
ps -xu | grep target
perf top -e cpu-clock -p 29713
發(fā)現(xiàn) foo 占用 60%cpu時間,而bar占用40%時間,和for循環(huán)展示的大致一樣
perf record -e cpu-clock -g -p 29713
ctrl + c停止記錄,發(fā)現(xiàn)當前目錄下保存了文件perf.data
使用report查看
perf report -i perf.data
對比兩者差異,因為只是單純記錄兩次,代碼沒有修改,所以沒有差異
perf diff perf.data perf.data
perf script -i perf.data &> perf.unfold
FlameGraph/stackcollapse-perf.pl test_data/perf.unfold &> test_data/perf.folded
拷貝到主機端進行轉(zhuǎn)換成火焰圖
FlameGraph/flamegraph.pl test_data/perf.folded > test_data/perf.svg
大家可以看到這個cpu占用關(guān)系,火焰圖的頂層是個大平層,說明這段代碼cpu單個函數(shù)foo和bar占用率太高,這段代碼優(yōu)化空間很大。
結(jié)語
這就是我自己的一些perf使用分享。如果大家有更好的想法和需求,也歡迎大家加我好友交流分享哈。
作者:良知猶存,白天努力工作,晚上原創(chuàng)公號號主。公眾號內(nèi)容除了技術(shù)還有些人生感悟,一個認真輸出內(nèi)容的職場老司機,也是一個技術(shù)之外豐富生活的人,攝影、音樂 and 籃球。關(guān)注我,與我一起同行。