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

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

他也是china-pub等據銷售評估的2008年度「十大暢銷經典」,「十佳原創精品」圖書《Linux設備驅動開發詳解》的做者和《Essential Linux Device Driver》的譯者。同時書寫了不少技術文章,是51CTO 2012年度「十大傑出IT博客」得主及51CTO、CSDN的專家博主。他也熱衷於開源項目,正在開發LEP(Linux Easy Profiling,www.linuxep.com)項目,並但願得到更多人的參與和幫助。linux

什麼是火焰圖

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

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

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

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

火焰圖的生成過程是:bash

  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();
}
複製代碼

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

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


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

相關文章
相關標籤/搜索