一、簡介
堆棧跟蹤,也稱爲堆棧回溯跟蹤或調用跟蹤,是顯示代碼流的一系列函數. 堆棧跟蹤可以用於瞭解導致事件的代碼路徑, 以及對內核和用戶代碼進行性能分析(profiling)以觀察執行時間.
舉例說明:
func_c() # 當前函數
func_b()
func_a() # 堆棧的底部
二、堆棧遍歷
BPF提供了用於記錄堆棧跟蹤的特殊映射類型,並且可以使用基於幀指針或基於ORC的堆棧遍歷來獲取它們
1. 基於幀指針
對於 x86-64 架構,共有16個64位通用寄存器。其中%RPB
是棧幀指針,用於標識當前棧幀的起始位置(棧底)。
幀指針技術遵循的慣例是: 始終可以在寄存器 x86_64 上的%RBP
中找到棧幀的起始位置,並且將返回地址存儲在與所存儲RBP的已知偏移量(+8)處。 這意味着,任何中斷程序的調試器或跟蹤器都可以讀取%RBP
,然後通過遍歷%RBP
鏈表並以已知的偏移量獲取地址來輕鬆獲取堆棧跟蹤. [TODO: 這部分內容暫未理解, 待補充, 詳見原文 P101]
三、火焰圖
堆棧跟蹤的定時採樣可以收集成千上萬個堆棧,每個堆棧可以長達數十或數百行。
Linux perf profiler
將其總結爲調用樹,顯示每個路徑的百分比BCC profile
計算每個堆棧(唯一)的出現次數
首先我們考慮以下示例: 由輸出可見, 代碼路徑func_a() -> func_b() -> func_c()
出現了7次
func_e
func_d
func_b
func_a
1
func_b
func_a
2
func_c
func_b
func_a
7
1. 基本概念
火焰圖是基於上述結果產生的SVG 圖片,可以用來展示 CPU 的調用棧. 其具有以下特性:
- 每個框代表堆棧中的一個方法(棧楨), 最上面的框爲正在運行的方法
- y軸顯示堆棧深度(堆棧中的幀數)
- x軸沒有像大多數圖形一樣顯示從左到右的時間流逝, 從左到右的順序是按字母順序排序的幀,以最大程度地合併幀; 寬度代表了調用棧在全局出現的次數,次數代表着出現頻率
由於這是顯示CPU樣本的火焰圖,進一步分析可以得到:
- 我們最主要的關注點要放在方塊的寬度上:
func_c
:70%,func_b
: 20%,func_c
: 10%.func_a
和func_d
未直接在CPU上採樣 - 觀察火焰圖底部或中部方塊的寬度佔比意義不大, 例如
func_a
函數寬度爲100%, 但真正消耗時間的它的子調用是func_c
- 我們更應該關注的是火焰圖頂部的一些 平頂山,頂部說明它沒有子調用,方塊寬說明它耗時長。
2. 生成火焰圖
我們通過點擊和鼠標指向可以展示出更多的信息。下圖就是一個典型的火焰圖,從結構上,它是由多個大小和顏色各異的方塊構成,每個方塊上都有字符,它們底部連接在一塊,組成火焰的基底,頂部分出許多“小火苗”
# 生成腳本文件
[root@rumia ~] perf script -i perf.data &> perf.unfold
# 執行完成後生成perf.svg圖片,可以下載到本地,用瀏覽器打開 perf.svg
[root@rumia ~] ./FlameGraph/stackcollapse-perf.pl perf.unfold &> perf.folded
[root@rumia ~] ./FlameGraph/flamegraph.pl perf.folded > perf.svg
3. IDEA 結合火焰圖
使用前注意Idea版本, 具體不太清楚哪個版本, 最新的肯定是可以~
a. 開啓火焰圖
- 按下快捷鍵
shift alt command + /
- 選擇
4 Experimental features
- 勾選
idea.profiler.enbaled
b. 運行
示例代碼:
public static void main(String[] args) {
Map map = new HashMap();
for (int i = 0; i < 10; i++) {
byte[] b = new byte[1024*1024];
map.put(i,b);
}
System.out.println(map);
}
-
選擇
CPU Profiler
執行程序, 然後就可以查看堆棧火焰圖 -
選擇
Allocation Profiler
執行程序, 然後就可以查看堆分配火焰圖: 此處調用堆棧的寬度與堆內存分配量成正比. 最頂部的調用堆棧元素代表分配的數據類型,即數組(byte [])或對象(StandardFilter)