關鍵詞:Flame Graph、perf、perl。html
FlameGraph是由BrendanGregg開發的一款開源可視化性能分析工具,形象的成爲火焰圖。ios
從底向上像火苗同樣逐漸變小,也反映了相互之間的包含關係,下面的框條包含上面內容。git
通過FlameGraph.git處理,最終生成矢量SVG圖形,能夠形象的看出不一樣部分佔用狀況,以及包含與被包含狀況。github
除了反應CPU使用狀況的CPU FlameGraph,還有幾種Flame Graph:Memory Flame Graph、Off-CPU Flame Graph、Hot/Cold Flame Graph、Differential Flame Graph。編程
本文目的是記錄如何使用Flame Graph;而後對其流程進行簡單分析,瞭解其數據前因後果;最後分析測試結果。瀏覽器
基本上作到知其然知其因此然。ide
構造測試程序以下,能夠啓動5個線程。svg
每一個線程都有本身的thread_funcx(),while(1)裏面再調用函數。函數
在8核CPU上執行,預測應該每一個thread_funcx()都會佔用相同的比例,由於都是100%佔用CPU,而後裏面的函數比例呈現階梯形。工具
#include <stdio.h> #include <pthread.h> #define LOOP_COUNT 1000000 void func_a(void) { int i; for(i=0; i<LOOP_COUNT; i++); } void func_b(void) { int i; for(i=0; i<LOOP_COUNT; i++); func_a(); } void func_c(void) { int i; for(i=0; i<LOOP_COUNT; i++); func_b(); } void func_d(void) { int i; for(i=0; i<LOOP_COUNT; i++); func_c(); } void func_e(void) { int i; for(i=0; i<LOOP_COUNT; i++); func_d(); } void* thread_fun1(void* param) { while(1) { int i; for(i=0;i<LOOP_COUNT;i++); func_a(); } } void* thread_fun2(void* param) { while(1) { int i; for(i=0;i<LOOP_COUNT;i++); func_b(); } } void* thread_fun3(void* param) { while(1) { int i; for(i=0;i<LOOP_COUNT;i++); func_c(); } } void* thread_fun4(void* param) { while(1) { int i; for(i=0;i<LOOP_COUNT;i++); func_d(); } } void* thread_fun5(void* param) { while(1) { int i; for(i=0;i<LOOP_COUNT;i++); func_e(); } } int main(void) { int ret; pthread_t tid1, tid2, tid3, tid4, tid5; ret=pthread_create(&tid1, NULL, thread_fun1, NULL); if(ret==-1){ printf("Create pthread failed.\n"); return -1; } ret=pthread_create(&tid2, NULL, thread_fun2, NULL); if(ret==-1){ printf("Create pthread failed.\n"); return -1; } ret=pthread_create(&tid3, NULL, thread_fun3, NULL); if(ret==-1){ printf("Create pthread failed.\n"); return -1; } ret=pthread_create(&tid4, NULL, thread_fun4, NULL); if(ret==-1){ printf("Create pthread failed.\n"); return -1; } ret=pthread_create(&tid5, NULL, thread_fun5, NULL); if(ret==-1){ printf("Create pthread failed.\n"); return -1; } if(pthread_join(tid1,NULL)!=0){ printf("pthrad join failed.\n"); return -1; } if(pthread_join(tid2,NULL)!=0){ printf("pthrad join failed.\n"); return -1; } if(pthread_join(tid3,NULL)!=0){ printf("pthrad join failed.\n"); return -1; } if(pthread_join(tid4,NULL)!=0){ printf("pthrad join failed.\n"); return -1; } if(pthread_join(tid5,NULL)!=0){ printf("pthrad join failed.\n"); return -1; } return 0; }
編譯而後執行結果:
gcc createFlame.c -o createFlame -pthread
./createFlame
在sudo su權限中進行perf record和FlameGraph生成;-F 999採樣率999Hz,-a包括全部CPU,-g使能call-graph錄製,-- sleep 60記錄60秒時長。
perf record -F 999 -a -g -- sleep 60 perf script | ./stackcollapse-perf.pl | ./flamegraph.pl > out.svg
在瀏覽器中查看結果以下:
能夠看出createFlame應用,調用start_thread建立線程,五個線程函數佔用相等寬度。
線程函數如下的層級調用寬度相差基本一致。
使用perf report -g查看start_thread,而後逐級展開調用及其佔比。
整個start_thread佔據99%,而後5個線程均分,由於每一個都獨佔一個CPU。
每一個線程裏面函數佔比,與FlameGraph中一致。
鼠標移動到FlameGraph框圖上時,會顯示對應進程或函數的被採樣信息。
若是點擊框圖,則以其爲基礎展開,放大顯示後面的找關係。已達到縮放,顯示細節和總體。
在右上角Search或者Ctrl+F,能夠在FlameGraph中查找相應符號的框圖。
從perf record輸出的perf.data,到最終生成out.svg文件,能夠分爲三步:1.perf script、2.stackcollapse-perf.pl、3.flamegraph.pl。
若是要詳細瞭解其如何一步一步解析字符串,到最終生成svg矢量圖形能夠閱讀stackcollapse-perf.pl和flamegraph.pl兩個perl腳本。
下面藉助構造僞數據,來理解其流程。
perf script將perf record的記錄轉換成可讀的採樣記錄,每個下采樣記錄包含應用名稱、以及採樣到的stack信息。
進程名後的進程ID、CPU號、時間戳、cycles數目都是無用信息,下面的stack也只有函數名有效。
createFlame 0 [0] 0.0: 0 cycles: 000 func_a (xxx) 000 func_b (xxx) 000 func_c (xxx) 000 func_d (xxx) 000 func_e (xxx) 000 thread_fun5 (xxx) 000 start_thread (xxx)
構造一份perf script生成的僞數據,來分析流程以及明白FlameGraph的含義。
createFlame 0 [0] 0.0: 0 cycles: 000 thread_fun1 (xxx) 000 start_thread (xxx) createFlame 0 [0] 0.0: 0 cycles: 000 func_a (xxx) 000 thread_fun1 (xxx) 000 start_thread (xxx) createFlame 0 [0] 0.0: 0 cycles: 000 thread_fun2 (xxx) 000 start_thread (xxx) createFlame 0 [0] 0.0: 0 cycles: 000 func_b (xxx) 000 thread_fun2 (xxx) 000 start_thread (xxx) createFlame 0 [0] 0.0: 0 cycles: 000 func_a (xxx) 000 func_b (xxx) 000 thread_fun2 (xxx) 000 start_thread (xxx) createFlame 0 [0] 0.0: 0 cycles: 000 thread_fun3 (xxx) 000 start_thread (xxx) createFlame 0 [0] 0.0: 0 cycles: 000 func_c (xxx) 000 thread_fun3 (xxx) 000 start_thread (xxx) createFlame 0 [0] 0.0: 0 cycles: 000 func_b (xxx) 000 func_c (xxx) 000 thread_fun3 (xxx) 000 start_thread (xxx) createFlame 0 [0] 0.0: 0 cycles: 000 func_a (xxx) 000 func_b (xxx) 000 func_c (xxx) 000 thread_fun3 (xxx) 000 start_thread (xxx) createFlame 0 [0] 0.0: 0 cycles: 000 thread_fun4 (xxx) 000 start_thread (xxx) createFlame 0 [0] 0.0: 0 cycles: 000 func_d (xxx) 000 thread_fun4 (xxx) 000 start_thread (xxx) createFlame 0 [0] 0.0: 0 cycles: 000 func_c (xxx) 000 func_d (xxx) 000 thread_fun4 (xxx) 000 start_thread (xxx) createFlame 0 [0] 0.0: 0 cycles: 000 func_b (xxx) 000 func_c (xxx) 000 func_d (xxx) 000 thread_fun4 (xxx) 000 start_thread (xxx) createFlame 0 [0] 0.0: 0 cycles: 000 func_a (xxx) 000 func_b (xxx) 000 func_c (xxx) 000 func_d (xxx) 000 thread_fun4 (xxx) 000 start_thread (xxx) createFlame 0 [0] 0.0: 0 cycles: 000 thread_fun5 (xxx) 000 start_thread (xxx) createFlame 0 [0] 0.0: 0 cycles: 000 func_e (xxx) 000 thread_fun5 (xxx) 000 start_thread (xxx) createFlame 0 [0] 0.0: 0 cycles: 000 func_d (xxx) 000 func_e (xxx) 000 thread_fun5 (xxx) 000 start_thread (xxx) createFlame 0 [0] 0.0: 0 cycles: 000 func_c (xxx) 000 func_d (xxx) 000 func_e (xxx) 000 thread_fun5 (xxx) 000 start_thread (xxx) createFlame 0 [0] 0.0: 0 cycles: 000 func_b (xxx) 000 func_c (xxx) 000 func_d (xxx) 000 func_e (xxx) 000 thread_fun5 (xxx) 000 start_thread (xxx) createFlame 0 [0] 0.0: 0 cycles: 000 func_a (xxx) 000 func_b (xxx) 000 func_c (xxx) 000 func_d (xxx) 000 func_e (xxx) 000 thread_fun5 (xxx) 000 start_thread (xxx)
stackcollapse-perf.pl將perf script生成的多行stack記錄轉換成一行,函數之間用逗號隔開,最後的記錄採樣次數用空格隔開。
能夠經過./stackcollapse-perf.pl -h查看幫助,查看cat perf_fake.txt | ./stackcollapse-perf.pl輸出。
能夠清晰地看出棧的關係和採樣到的次數。
createFlame;start_thread;thread_fun1 1 createFlame;start_thread;thread_fun1;func_a 1 createFlame;start_thread;thread_fun2 1 createFlame;start_thread;thread_fun2;func_b 1 createFlame;start_thread;thread_fun2;func_b;func_a 1 createFlame;start_thread;thread_fun3 1 createFlame;start_thread;thread_fun3;func_c 1 createFlame;start_thread;thread_fun3;func_c;func_b 1 createFlame;start_thread;thread_fun3;func_c;func_b;func_a 1 createFlame;start_thread;thread_fun4 1 createFlame;start_thread;thread_fun4;func_d 1 createFlame;start_thread;thread_fun4;func_d;func_c 1 createFlame;start_thread;thread_fun4;func_d;func_c;func_b 1 createFlame;start_thread;thread_fun4;func_d;func_c;func_b;func_a 1 createFlame;start_thread;thread_fun5 1 createFlame;start_thread;thread_fun5;func_e 1 createFlame;start_thread;thread_fun5;func_e;func_d 1 createFlame;start_thread;thread_fun5;func_e;func_d;func_c 1 createFlame;start_thread;thread_fun5;func_e;func_d;func_c;func_b 1 createFlame;start_thread;thread_fun5;func_e;func_d;func_c;func_b;func_a 1
那麼stackcollapse-perf.pl的數據通過flamegraph.pl處理以後又是什麼樣子呢?
能夠看出svg圖形,就像stackcollapse-perf.pl每一行豎向顯示。
那麼簡單修改一下,將thread_fun5的func_a的stack重複4次,圖形會變成什麼樣子呢?
能夠看出thread_fun5的func_a變得更寬了。
因此不難理解,Flame Graph縱向表示一次調用棧深度,調用關係從下到上;Flame Graph橫向寬度表示被perf record採樣到的次數。
全部的FlameGraph都是統計採樣結果,根據進程、函數棧進行匹配,一樣棧的採樣計數累加。
FlameGraph的實際應用除了查看CPU使用狀況以外,還有經過監控內存分配/釋放函數的MemoryFlameGraph;
記錄進程由於IO、喚醒等耗費時間的Off-CPU FlameGraph;
以及將CPU FlameGraph和Off-CPU FlameGraph進行合併的Hot/Cold FlameGraph;
對兩次不一樣測試進行比較的DifferentialFlameGraph。
以前對CPU FlameGraph進行了介紹,下面詳細介紹其他四種FlameGraph的使用。
《Memory Leak (and Growth) Flame Graphs》關於內存的FlameGraph和CPU FlameGraph的區別在於CPU是採樣,Memory跟蹤內存trace events,好比malloc()/free()/realloc()/calloc()/brk()/mmap()。
而後在對調用棧進行統計,顯示FlameGraph。其本質上是同樣的。
perf record -e syscalls:sys_enter_mmap -a -g -- sleep 120 perf script | ./stackcollapse-perf.pl | ./flamegraph.pl --color=mem \ --title="Heap Expansion Flame Graph" --countname="calls" > out_mmap.svg
結果以下:
但從實際來看這張圖並不能反映Memory Leak,也不能準確反映Memory Grouth。
由於只是記錄mmap()的次數,沒有記錄每次大小;同時沒有記錄munmap()的次數。
記得以前Debug過內存泄漏問題:運行過一段時間,發現總的內存在增長。查看/proc/meminfo大概是slab內存泄漏,而後查看一下/proc/slabinfo看出是kmalloc-64在不停增長。
因此藉助tracing/events/kmem/kmalloc和kfree兩個events,觀察是哪一個進程在泄漏內存,同時修改call_site從顯示地址編程顯示符號。
如何肯定內存泄漏呢?
以進程做爲組,kmalloc()分配大小累加;若是有kfree(),經過ptr匹配從累計值中減去對應kmalloc()大小。
這樣在運行一段時間事後,每一個進程的累計值就是增量,能夠很輕鬆的肯定增量是多少,以及每一個增量的符號表。
和CPU FlameGraph相反,Off-CPU FlameGraph反映的是進程沒有在CPU上運行的時間都在幹嗎,這也是影響進程性能的關鍵因素。
好比進程時間片用完致使的進程切換、映射到內存的IO操做、調度延遲等。
《Off-CPU Flame Graphs》按部就班的介紹了IO形成的Off-CPU時間、包括IO延遲的Off-CPU時間、進程喚醒延時,以及展現進程之間喚醒點棧關係的Chain Graphs。
好比查看Block I/O次數的FlameGraph,這個只能作個參考。若是想要更準確的看IO延遲時間,還須要藉助文中提到的biostacks、fileiostacks等工具。
sudo perf record -e block:block_rq_insert -a -g -- sleep 30 sudo perf script --header | ./stackcollapse-perf.pl | ./flamegraph.pl --color=io --title="Block I/O Flame Graph" --countname="I/O" > out.svg
結果以下:
《Hot/Cold FlameGraph》將On-CPU FlameGraph和Off-CPU FlameGraph融合到一張圖中,這樣就能夠一目瞭然時間都耗費在哪裏了。
可是目前生成的結果分析起來仍然比較困難,還處在實驗階段。
《Differential FlameGraph》比較兩份FlameGraph,用於比較兩個版本差別,更好地肯定性能regression。
實際環境中的Differential FlameGraph較難分析,這裏構造兩個FlameGraph而後進行Differential比較。
分別構造僞數據out.folded1和out.folded2以下:
out.folded1
createFlame;start_thread;thread_fun1 1 createFlame;start_thread;thread_fun1;func_a 1 createFlame;start_thread;thread_fun2 1 createFlame;start_thread;thread_fun2;func_b 1 createFlame;start_thread;thread_fun2;func_b;func_a 1 createFlame;start_thread;thread_fun3 1 createFlame;start_thread;thread_fun3;func_c 1 createFlame;start_thread;thread_fun3;func_c;func_b 1 createFlame;start_thread;thread_fun3;func_c;func_b;func_a 1 createFlame;start_thread;thread_fun4 1 createFlame;start_thread;thread_fun4;func_d 1 createFlame;start_thread;thread_fun4;func_d;func_c 1 createFlame;start_thread;thread_fun4;func_d;func_c;func_b 1 createFlame;start_thread;thread_fun4;func_d;func_c;func_b;func_x 1 createFlame;start_thread;thread_fun4;func_d;func_c;func_b;func_a 5 createFlame;start_thread;thread_fun5 1 createFlame;start_thread;thread_fun5;func_e 1 createFlame;start_thread;thread_fun5;func_e;func_d 1 createFlame;start_thread;thread_fun5;func_e;func_d;func_c 1 createFlame;start_thread;thread_fun5;func_e;func_d;func_c;func_b 1 createFlame;start_thread;thread_fun5;func_e;func_d;func_c;func_b;func_a 1
out.folded2
createFlame;start_thread;thread_fun1 1 createFlame;start_thread;thread_fun1;func_a 1 createFlame;start_thread;thread_fun2 1 createFlame;start_thread;thread_fun2;func_b 1 createFlame;start_thread;thread_fun2;func_b;func_a 1 createFlame;start_thread;thread_fun3 1 createFlame;start_thread;thread_fun3;func_c 1 createFlame;start_thread;thread_fun3;func_c;func_b 1 createFlame;start_thread;thread_fun3;func_c;func_b;func_a 1 createFlame;start_thread;thread_fun4 1 createFlame;start_thread;thread_fun4;func_d 1 createFlame;start_thread;thread_fun4;func_d;func_c 1 createFlame;start_thread;thread_fun4;func_d;func_c;func_b 1 createFlame;start_thread;thread_fun4;func_d;func_c;func_b;func_a 1 createFlame;start_thread;thread_fun5 1 createFlame;start_thread;thread_fun5;func_e 1 createFlame;start_thread;thread_fun5;func_e;func_d 1 createFlame;start_thread;thread_fun5;func_e;func_d;func_c 1 createFlame;start_thread;thread_fun5;func_e;func_d;func_c;func_b 1 createFlame;start_thread;thread_fun5;func_e;func_d;func_c;func_b;func_x 1 createFlame;start_thread;thread_fun5;func_e;func_d;func_c;func_b;func_a 5
分別生成二者FlameGraph及Differential FlameGraph。
./flamegraph.pl < out.folded1 > out_1.svg ./flamegraph.pl < out.folded2 > out_2.svg ./difffolded.pl out.folded1 out.folded2 | ./flamegraph.pl > diff.svg
./difffolded.pl out.folded2 out.folded1 | ./flamegraph.pl > diff2.svg
圖1
圖2
圖1和圖2反映了兩組數據的差別,圖2相比圖1thread_fun4少了func_x,減少了func_a;thread_fun5的func_a增大了,多了func_x。
下面是圖1相對於圖2的Differential FlameGraph,能夠看出輪廓基本和圖2一致。
圖2丟掉的thread_fun4的func_x,不顯示;func_a變小用藍色標識。圖2新增的thread_fun5的fun_x,thread_fun5的func_a用紅色標識。
圖3
而後再以圖1爲基礎進行查分,以下圖:
圖4
CPU FlameGraph用於查找程序執行的熱點,找出性能瓶頸。Memory FlameGraph用於簡單分析內存泄漏或者增加趨勢。
相對於CPU FlameGraph,Off-CPU FlameGraph能找出進程好在CPU以外的時間,對於提升進程性能找出浪費時間有效。
Hot/Cold FlameGraph將CPU FlameGraph和Off-CPU FlameGraph二者融合到一張圖中,更清晰的展現進程時間分配。
Differential FlameGraph可用於性能Regression對比。
參考文檔:
《Flame Graphs》:關於FlameGraph的前因後果,及其詳細介紹彙總。
《The Flame Graph》:發表在acm.org文章,This visualization of software execution is a new necessity for performance profiling and debugging。