初識火焰圖

火焰圖就像是給一個軟件系統拍的 X 光照片,
可以很自然地把時間和空間兩個維度上的信息融合在一張圖上,
以非常直觀的形式展現出來,
從而反映系統在性能方面的很多定量的統計規律。

擴展:章亦春 《動態追蹤技術漫談》

什么是火焰圖

火焰圖只是一種統計數據的展現方式,它和直方圖、曲線圖沒有什么本質的區別。最經典的火焰圖是統計某一個軟件的所有代碼路徑在 CPU 上面的時間分布。

下面是redis-server的火線圖


img
img

火焰圖中展示的是什么數據

拿上圖為例,圖中展示的就是Redis-server運行時的部分代碼路徑,每個方塊代表一個函數調用,水平方向代表CPU耗時占比,垂直方向代表函數的調用關系,實際上,這張圖就是redis-server進程一段時間內的??煺盏木酆稀?/p>

注意:圖中沒有任何關于時間的信息,它只是進程運行過程中各個函數的時間占比。

怎樣生成火焰圖(On-CPU)

  1. 周期性的采集棧數據
    可選的工具
    • systemtap
    • perf
    • 其他(不同編程語言,不同的系統架構)
  2. 以棧為維度,做聚合統計
    Brendan Gregg 大大已經提供
  3. 生成火線圖(svg)
    Brendan Gregg 大大已經提供

可見,這里有兩個難點

  • 獲取棧數據
  • 解釋棧數據

使用systemtap采集棧數據

systemtap腳本

probe begin {
    warn(sprintf("Tracing %d (/root/redis-3.2.8/src/redis-server) in user-space only...\n", target()))
}


global bts;
global quit = 0;

probe timer.profile {
    if (pid() == target()) {
        if (!quit) {
            bts[ubacktrace()] <<< 1;

        } else {

            foreach (bt in bts- limit 1024) {
                print_ustack(bt);
                printf("\t%d\n", @count(bts[bt]));
            }

            exit()
        }
    }
}

probe timer.s(5) {
    nstacks = 0
    foreach (bt in bts limit 1) {
        nstacks++
    }

    if (nstacks == 0) {
        warn("No backtraces found. Quitting now...\n")
        exit()

    } else {
        warn("Time's up. Quitting now...(it may take a while)\n")
        quit = 1
    }
}

腳本中,使用了兩個定時器,一個定時器負責周期性的采集棧數據,另一個定時器是結束腳本,并觸發輸出采集數據。

采樣數據

下面就是systemtap腳本輸出(redis-server的棧數據)

 0x7f96f75e8c3d : __open_nocancel+0x24/0x57 [/usr/lib64/libpthread-2.17.so]
 0x42cd9d : zmalloc_get_rss+0x58/0x159 [/root/redis-3.2.8/src/redis-server]
 0x422a15 : serverCron+0xff/0x84c [/root/redis-3.2.8/src/redis-server]
 0x41d394 : processTimeEvents+0x1a1/0x1ff [/root/redis-3.2.8/src/redis-server]
 0x41d6b7 : aeProcessEvents+0x2c5/0x2cd [/root/redis-3.2.8/src/redis-server]
 0x41d7d0 : aeMain+0x48/0x55 [/root/redis-3.2.8/src/redis-server]
 0x429f3a : main+0x6ec/0x707 [/root/redis-3.2.8/src/redis-server]
 0x7f96f723ab35 : __libc_start_main+0xf5/0x1c0 [/usr/lib64/libc-2.17.so]
 0x4192a9 : _start+0x29/0x30 [/root/redis-3.2.8/src/redis-server]
    1
 0x7f96f7310d13 : __epoll_wait_nocancel+0x2a/0x57 [/usr/lib64/libc-2.17.so]
 0x41c9d6 : aeApiPoll+0x85/0x15f [/root/redis-3.2.8/src/redis-server]
 0x41d59c : aeProcessEvents+0x1aa/0x2cd [/root/redis-3.2.8/src/redis-server]
 0x41d7d0 : aeMain+0x48/0x55 [/root/redis-3.2.8/src/redis-server]
 0x429f3a : main+0x6ec/0x707 [/root/redis-3.2.8/src/redis-server]
 0x7f96f723ab35 : __libc_start_main+0xf5/0x1c0 [/usr/lib64/libc-2.17.so]
 0x4192a9 : _start+0x29/0x30 [/root/redis-3.2.8/src/redis-server]
    1
 0x7f96f75e849d : __read_nocancel+0x24/0x57 [/usr/lib64/libpthread-2.17.so]
 0x42cdc9 : zmalloc_get_rss+0x84/0x159 [/root/redis-3.2.8/src/redis-server]
 0x422a15 : serverCron+0xff/0x84c [/root/redis-3.2.8/src/redis-server]
 0x41d394 : processTimeEvents+0x1a1/0x1ff [/root/redis-3.2.8/src/redis-server]
 0x41d6b7 : aeProcessEvents+0x2c5/0x2cd [/root/redis-3.2.8/src/redis-server]
 0x41d7d0 : aeMain+0x48/0x55 [/root/redis-3.2.8/src/redis-server]
 0x429f3a : main+0x6ec/0x707 [/root/redis-3.2.8/src/redis-server]
 0x7f96f723ab35 : __libc_start_main+0xf5/0x1c0 [/usr/lib64/libc-2.17.so]
 0x4192a9 : _start+0x29/0x30 [/root/redis-3.2.8/src/redis-server]
    1

這是其中一種棧數據的輸出格式,Brendan Gregg大大的工具可以支持好幾種輸出格式,有興趣可以查看源碼。

聚合采樣數據

_start;__libc_start_main;main;aeMain;aeProcessEvents;aeApiPoll;__epoll_wait_nocancel 1
_start;__libc_start_main;main;aeMain;aeProcessEvents;processTimeEvents;serverCron;zmalloc_get_rss;__open_nocancel 1
_start;__libc_start_main;main;aeMain;aeProcessEvents;processTimeEvents;serverCron;zmalloc_get_rss;__read_nocancel 1

小結

由于systemtap安裝較為繁瑣,因此本文作者開發了一個shell腳本,使用perf完成以上步驟。
源碼

生成一個火焰圖很容易,難的是從火焰圖中發現問題,并且能夠給出較為合理的解釋,再進一步給出優化方案。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容