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

騰訊雲加社群發表於2017-11-22

作者簡介:宋寶華,他有10幾年的Linux開發經驗。他長期在大型企業擔任一線工程師和系統架構師,編寫大量的Linux程式碼,並負責在gerrit上review其他同事的程式碼。Barry Song是Linux的活躍開發者,是某些核心版本的最活躍開發者之一(如lwn.net/Articles/39…lwn.net/Articles/42… ),也曾是一ARM SoC系列在Linux mainline的maintainer。

他也是china-pub等據銷售評估的2008年度“十大暢銷經典”,“十佳原創精品”圖書《Linux裝置驅動開發詳解》的作者和《Essential Linux Device Driver》的譯者。同時書寫了很多技術文章,是51CTO 2012年度“十大傑出IT部落格”得主及51CTO、CSDN的專家博主。他也熱衷於開源專案,正在開發LEP(Linux Easy Profiling,www.linuxep.com)專案,並希望獲得更多人的參與和幫助。

什麼是火焰圖

火焰圖(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 offCPU Time Flame Graphs》。

關於火焰圖的更多細節和更多種火焰圖各自的功能,可以訪問:
www.brendangregg.com/flamegraphs…

本文來自 Linuxerr 微信公眾號

相關閱讀

宋寶華: Linux 效能調優的分析與實戰

謝寶友:深入理解 RCU 之概念

張亦鳴 : eBPF 簡史 (下篇)


此文已由作者授權騰訊雲技術社群釋出,轉載請註明原文出處

原文連結:https://cloud.tencent.com/community/article/934261?utm_source=juejin


海量技術實踐經驗,盡在騰訊雲社群




相關文章