宋寶華:火焰圖:全局視野的Linux性能剖析

簡介

火焰圖(Flame Graph)是由Linux性能優化大師Brendan Gregg發明的,本文用最簡單的實例講解什麼是火焰圖,怎麼畫出來火焰圖,火焰圖的優點是什麼。

什麼是火焰圖

火焰圖(Flame Graph)是由Linux性能優化大師Brendan Gregg發明的,和所有其他的trace和profiling方法不同的是,Flame Graph以一個全局的視野來看待時間分佈,它從底部往頂部,列出所有可能的調用棧。其他的呈現方法,一般只能列出單一的調用棧或者非層次化的時間分佈。

我最快樂的童年時代,每逢冬天,尤其是春節的時候,和一家人圍坐在火堆旁邊烤火。這已經成爲最美好的回憶,其實人生追求的快樂非常簡單。火焰圖的火焰首先來自於根,然後以火苗的形式往上面竄。可以把從靠近地面的根到頂上的每個火苗,想想成一個調用棧。由於火苗有很多根,這正好也和現實生活中程序的執行邏輯相似。


以典型的分析CPU時間花費到哪個函數的on-cpu火焰圖爲例來展開。

CPU火焰圖中的每一個方框是一個函數,方框的長度,代表了它的執行時間,所以越寬的函數,執行越久。火焰圖的樓層每高一層,就是更深一級的函數被調用,最頂層的函數,是葉子函數。



火焰圖的生成過程是:

  1. 先trace系統,獲取系統的profiling數據 

  2. 用腳本來繪製


系統的profiling數據獲取,可以選擇最流行的perf record,而後把採集的數據進行加工處理,繪製爲火焰圖。其中第二步的繪製火焰圖的腳本程序,通過如下方式獲取:

git clone https://github.com/brendangregg/FlameGraph

火焰圖案例

廢話不多說,直接從最簡單的例子開始說起。talk is cheap, show you the cde,代碼如下:

c()

{

    for(int i=0;i<1000;i++);

}

b()

{

    for(int i=0;i<1000;i++);

    c();

}

a()

{

    for(int i=0;i<1000;i++);

    b();

}

則這三個函數,在火焰圖中呈現的樣子爲:


a()的2/3的時間花在b()上面,而b()的1/3的時間花在c()上面。很多個這樣的a->b->c的火苗堆在一起,就構成了火焰圖。


進一步理解火焰圖的最好方法仍然是通過一個實際的案例,下面的程序創建2個線程,兩個線程的handler都是thread_fun(),之後thread_fun()調用fun_a()、fun_b()、fun_c(),而fun_a()又會調用fun_d():

/*

 * One example to demo flamegraph

 *

 * Copyright (c) Barry Song

 *

 * Licensed under GPLv2

 */


#include <pthread.h>


func_d()

{

    int i;

    for(i=0;i<50000;i++);

}


func_a()

{

    int i;

    for(i=0;i<100000;i++);

    func_d();

}


func_b()

{

    int i;

    for(i=0;i<200000;i++);

}


func_c()

{

    int i;

    for(i=0;i<300000;i++);

}


void* thread_fun(void* param)

{

    while(1) {

        int i;

        for(i=0;i<100000;i++);

        

        func_a();

        func_b();

        func_c();

    }

}


int main(void)

{

    pthread_t tid1,tid2;

    int ret;

    

    ret=pthread_create(&tid1,NULL,thread_fun,NULL);

    if(ret==-1){

        ...

    }

    

    ret=pthread_create(&tid2,NULL,thread_fun,NULL);

    ...

    

    if(pthread_join(tid1,NULL)!=0){

        ...

    }

    

    if(pthread_join(tid2,NULL)!=0){

        ...

    }

    

    return 0;

}

先看看不用火焰圖的缺點在哪裏。

如果不用火焰圖,我們也可以用類似perf top這樣的工具分析出來CPU時間主要花費在哪裏了:

$gcc exam.c -pthread

$./a.out&

$sudo perf top

perf top的顯示結果如下:


perf top提示出來了fun_a()、fun_b()、fun_c(), fun_d(),thread_func()這些函數內部的代碼是CPU消耗大戶,但是它缺乏一個全局的視野,我們無法看出全局的調用棧,也弄不清楚這些函數之間的關係。火焰圖則不然,我們用下面的命令可以生成火焰圖(以root權限運行):

perf record -F 99 -a -g -- sleep 60

perf script | ./stackcollapse-perf.pl > out.perf-folded

./flamegraph.pl out.perf-folded > perf-kernel.svg

上述程序捕獲系統的行爲60秒鐘,最後調用flamegraph.pl生成一個火焰圖perf-kernel.svg,用看圖片的工具就可以打開這個svg。

上述火焰圖顯示出了a.out中,thread_func()、func_a()、func_b()、fun_c()和func_d()的時間分佈。

從上述火焰圖可以看出,雖然thread_func()被兩個線程調用,但是由於thread_func()之前的調用棧是一樣的,所以2個線程的thread_func()調用是合併爲同一個方框的。

更多閱讀

除了on-cpu的火焰圖以外,off-cpu的火焰圖,對於分析系統堵在IO、SWAP、取得鎖方面的幫助很大,有利於分析系統在運行的時候究竟在等待什麼,系統資源之間的彼此伊伴。

比如,下面的火焰圖顯示,nginx的吞吐能力上不來的很多程度原因在於sem_wait()等待信號量。



上圖摘自Yichun Zhang (agentzh)的《Introduction to off-CPU Time Flame Graphs》。


關於火焰圖的更多細節和更多種火焰圖各自的功能,可以訪問: 

http://www.brendangregg.com/flamegraphs.html


本文首發於Linuxer微信公衆號(ID: LinuxDev),轉發回blog。掃描二維碼關注Linuxer~


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章