引言
cpu無端占用高?應用程序響應慢?苦於沒有分析的工具?
oprofile利用cpu硬件層面提供的性能計數器(performance counter),通過計數采樣,幫助我們從進程、函數、代碼層面找出占用cpu的"罪魁禍首"。下面我們通過實例,了解oprofile的具體使用方法。
常用命令
使用oprofile進行cpu使用情況檢測,需要經過初始化、啟動檢測、導出檢測數據、查看檢測結果等步驟,以下為常用的oprofile命令。
初始化
- opcontrol --no-vmlinux : 指示oprofile啟動檢測后,不記錄內核模塊、內核代碼相關統計數據
- opcontrol --init : 加載oprofile模塊、oprofile驅動程序
檢測控制
- opcontrol --start : 指示oprofile啟動檢測
- opcontrol --dump : 指示將oprofile檢測到的數據寫入文件
- opcontrol --reset : 清空之前檢測的數據記錄
- opcontrol -h : 關閉oprofile進程
查看檢測結果
- opreport : 以鏡像(image)的角度顯示檢測結果,進程、動態庫、內核模塊屬於鏡像范疇
- opreport -l : 以函數的角度顯示檢測結果
- opreport -l test : 以函數的角度,針對test進程顯示檢測結果
- opannotate -s test : 以代碼的角度,針對test進程顯示檢測結果
- opannotate -s /lib64/libc-2.4.so : 以代碼的角度,針對libc-2.4.so庫顯示檢測結果
opreport輸出解析
正如以上命令解析所言,不加參數的opreport命令從鏡像的角度顯示cpu的使用情況:
linux # opreport CPU: Core 2, speed 2128.07 MHz (estimated) Counted CPU_CLK_UNHALTED events (Clock cycles when not halted) with a unit mask of 0x00 (Unhalted core cycles) count 100000 CPU_CLK_UNHALT.........| samples | %| ------------------------ 31645719 87.6453 no-vmlinux 4361113 10.3592 libend.so 7683 0.1367 libpython2.4.so.1.0 7046 0.1253 op_test ⋯⋯
以上列表按以下形式輸出:
samples | %|
----------------------------------------------------- 鏡像內發生的采樣次數 采樣次數所占總采樣次數的百分比 鏡像名稱
因我們在初始化時執行了"opcontrol --no-vmlinux"命令,指示oprofile不對模塊和內核進行檢測,因而在探測結果中,模塊和內核一同顯示成no-vmlinux鏡像。輸出中,libend.so和libpython2.4.so.1.0均為動態庫,op_test為進程。以上采樣數據表明,檢測時間內,cpu主要執行內核和模塊代碼,用於執行libend.so庫函數的比重亦較大,達到10%左右。
進一步地,我們可以查看到進程、動態庫中的每個函數在檢測時間內占用cpu的情況:
linux # opreport -l samples % image name app name symbol name 31645719 87.4472 no-vmlinux no-vmlinux /no-vmlinux 4361113 10.3605 libend.so libend.so endless 7046 0.1253 op_test op_test main ⋯⋯
以上輸出顯示消耗cpu的函數為libend.so庫中的endless函數,以及op_test程序中的main函數。
進行oprofile初始化時,若我們執行opcontrol --vmlinux=vmlinux-`uname -r`,指定oprofile對內核和內核模塊進行探測,在執行opreport查看檢測結果時,內核和內核模塊就不再顯示為no-vmlinux,而是內核和各個內核模塊作為單獨的鏡像,顯示相應cpu占用情況。
使用opannotate從代碼層看cpu占用情況
以上介紹了使用oprofile的opreport命令,分別從進程和函數層面查看cpu使用情況的方法。看到這里,有的同學可能有這樣的疑問:使用opreport,我找到了消耗cpu的進程A,找到了進程A中最消耗cpu的函數B,進一步地,是否有辦法找到函數B中最消耗cpu的那一行代碼呢?
oprofile中的opannotate命令可以幫助我們完成這個任務,結合具備調試信息的程序、帶有debuginfo的動態庫,opannotate命令可顯示代碼層面占用cpu的統計信息。下面我們通過幾個簡單的程序實例,說明opannotate命令的使用方法。
首先,我們需要一個消耗cpu的程序,該程序代碼如下:
//op_test.c extern void endless(); int main() { int i = 0, j = 0; for (; i < 10000000; i++ ) { j++; } endless(); return 0; }
該程序引用了外部函數endless,endless函數定義如下:
//end.c void endless() { int i = 0; while(1) { i++; } }
endless函數同樣很簡單,下面我們將定義了endless函數的end.c進行帶調試信息地編譯,並生成libend.so動態庫文件:
linux # gcc -c -g -fPIC end.c linux # gcc -shared -fPIC -o libend.so end.o linux # cp libend.so /usr/lib64/libend.so
接着,帶調試信息地編譯op_test.c,生成op_test執行文件:
linux # gcc -g -lend -o op_test op_test.c
之后,我們開啟oprofile進行檢測,並拉起op_test進程:
linux # opcontrol --reset linux # opcontrol --start linux # ./op_test &
在程序運行一段時間后,導出檢測數據,使用opannotate進行結果查看:
linux # opcontrol --dump linux # opannotate -s op_test /* * Total samples for file : "/tmp/lx/op_test.c" * * 7046 100.00 */ : int main() :{ /*main total : 7046 100.000 */ : int i = 0, j = 0; 6447 91.4987 : for (; i < 10000000; i++ ) : { 599 8.5013 : j++; : } : endless(); : return 0; :}
以上輸出表明,在op_test程序的main函數中,主要消耗cpu的是for循環所在行代碼,因該段代碼不僅包含變量i的自增運算,還將i與10000000進行比較。
下面顯示對自編動態庫libend.so的檢測結果:
linux # opannotate -s /usr/lib64/libend.so /* * Total samples for file : "/tmp/lx/end.c" * * 4361113 100.00 */ : void endless() : { : int i = 0; : while(1) : { 25661 0.6652 : i++; 4335452 99.3348 : } : }
查看c庫代碼占用cpu情況
以上使用opannotate,分別查看了應用程序代碼、自編動態庫代碼的cpu占用情況,對於c庫中的代碼,我們是否也能查看其消耗cpu的情況呢?
在使用oprofile查看c庫代碼信息前,需要安裝glibc的debuginfo包,安裝debuginfo包之后,我們即可以通過opannotate查看到c庫代碼,以下展示了malloc底層實現函數_int_malloc的部分代碼:
linux # opannotate -s /lib64/libc-2.4.so /* ----------------malloc--------------------- */ :Void_t * :_int_malloc( mstate av, size_t bytes ) :{ /* _int_malloc total: 118396 94.9249 */ ⋯⋯ : assert((fwd->size & NON_MAIN_ARENA) == 0); 115460 92.5709 : while((unsigned long)(size) < (unsigned long)(fwd->size)) { 1161 0.9308 : fwd = fwd->fd; : assert((fwd->size & NON_MAIN_ARENA) == 0); : } :}
在進行程序性能調優時,根據oprofile檢測到的c庫代碼占用cpu的統計信息,可以判別程序性能瓶頸是否由c庫代碼引起。若oprofile檢測結果顯示cpu被過多地用於執行c庫中的代碼,我們可進一步地采用修改c庫代碼、升級glibc版本等方法解決c庫引發的應用程序性能問題。
小結
本文介紹了使用oprofile工具從進程、函數和代碼層面檢測cpu使用情況的方法,對於代碼層面,分別介紹了查看程序代碼、自編動態庫代碼以及gblic代碼cpu統計情況的方法,中間過程使用到opcontrol、opreport、opannotate三個常用的oprofile命令。
當系統出現cpu使用率異常偏高情況時,oprofile不但可以幫助我們分析出是哪一個進程異常使用cpu,還可以揪出進程中占用cpu的函數、代碼。在分析應用程序性能瓶頸、進行性能調優時,我們可以通過oprofile,得出程序代碼的cpu使用情況,找到最消耗cpu的那部分代碼進行分析與調優,做到有的放矢。另外,進行程序性能調優時,我們不應僅僅關注自己編寫的上層代碼,也應考慮底層庫函數,甚至內核對應用程序性能的影響。
關於oprofile工具可用於分析的場景,本文僅介紹了cpu使用情況一種,我們還可以通過oprofile查看高速緩存的利用率、錯誤的轉移預測等信息,"opcontrol --list-events"命令顯示了oprofile可檢測到的所有事件,更多的oprofile使用方法,請參看oprofile manual。
Reference: oprofile manual