解讀 Golang pprof 報告

【導讀】在讀 go 的 pprof 報告時候如何理解裏面的數字?本文在官方文檔基礎上進一步解釋了這些指標的含義。

Profiling Go Programs(https://blog.golang.org/profiling-go-programs) 裏詳細舉例說明了如何看 pprof 報告,但並沒有清晰簡明的告知讀者提供數字的是什麼意思,所以本文做一個歸納筆記。

解讀 CPU

以文中提供的 CPU Profile 來舉例說明,我們使用go tool pprof -http=0.0.0.0:4231 havlak1 havalk1.prof來觀察

解讀 Top

(pprof) top10
Total: 2525 samples
    Flat  Flat%   Sum%      Cum   Cum% Name
     298  11.8%  11.8%      345  13.7% runtime.mapaccess1_fast64
     268  10.6%  22.4%     2124  84.1% main.FindLoops
     251   9.9%  32.4%      451  17.9% scanblock
     178   7.0%  39.4%      351  13.9% hash_insert
     131   5.2%  44.6%      158   6.3% sweepspan
     119   4.7%  49.3%      350  13.9% main.DFS
      96   3.8%  53.1%       98   3.9% flushptrbuf
      95   3.8%  56.9%       95   3.8% runtime.aeshash64
      95   3.8%  60.6%      101   4.0% runtime.settype_flush
      88   3.5%  64.1%      988  39.1% runtime.mallocgc

先了解是如何採樣的:

原文中沒有給出列名,這裏給了出來,下面是解釋:

解讀圖

解讀源碼

下面是在 pprof 交互 cli 界面看到的報告:

(pprof) list DFS
Total: 2525 samples
ROUTINE ====================== main.DFS in /home/rsc/g/benchgraffiti/havlak/havlak1.go
   119    697 Total samples (flat / cumulative)
     3      3  240: func DFS(currentNode *BasicBlock, nodes []*UnionFindNode, number map[*BasicBlock]int, last []int, current int) int {
     1      1  241:     nodes[current].Init(currentNode, current)
     1     37  242:     number[currentNode] = current
     .      .  243:
     1      1  244:     lastid := current
    89     89  245:     for _, target := range currentNode.OutEdges {
     9    152  246:             if number[target] == unvisited {
     7    354  247:                     lastid = DFS(target, nodes, number, last, lastid+1)
     .      .  248:             }
     .      .  249:     }
     7     59  250:     last[number[currentNode]] = lastid
     1      1  251:     return lastid
(pprof)

下面是在 Web 界面看到的報告(基本差不多,見這裏):

havlak1
Total: 5758 samples
main.DFS
/home/rsc/g/benchgraffiti/havlak/havlak1.go

Total:   225   2296 (flat / cumulative samples)
  235                return false
  236               }
  237               
  238               // DFS - Depth-First-Search and node numbering.
  239               //
  240      3      3 func DFS(currentNode *BasicBlock, nodes []*UnionFindNode, number map[*BasicBlock]int, last []int, current int) int {
  241     18     19  nodes[current].Init(currentNode, current)
  242           166  number[currentNode] = current
  243               
  244      2      2  lastid := current
  245    167    167  for _, target := range currentNode.OutEdges {
  246     17    508   if number[target] == unvisited {
  247     10   1157    lastid = DFS(target, nodes, number, last, lastid+1)
  248                 }
  249                }
  250      7    273  last[number[currentNode]] = lastid
  251      1      1  return lastid
  252               }
  253               
  254               // FindLoops
  255               //
  256               // Find loops and build loop forest using Havlak's algorithm, which

解讀內存

以文中提供的內存 Profile 來舉例說明,我們使用go tool pprof -http=0.0.0.0:4231 havlak3 havalk3.mprof來觀察。

pprof 提供了 4 種視角,默認是-inuse_space

解讀 Top

(pprof) top5
Total: 82.4 MB
    Flat  Flat%   Sum%      Cum   Cum% Name
    56.3  68.4%  68.4%     56.3  68.4% main.FindLoops
    17.6  21.3%  89.7%     17.6  21.3% main.(*CFG).CreateNode
     8.0   9.7%  99.4%     25.6  31.0% main.NewBasicBlockEdge
     0.5   0.6% 100.0%      0.5   0.6% itab
     0.0   0.0% 100.0%      0.5   0.6% fmt.init
(pprof)

採樣頻率:

照例我們加上列:

解讀源碼

和 CPU 源碼解讀差不多:

(pprof) list FindLoops
Total: 82.4 MB
ROUTINE ====================== main.FindLoops in /home/rsc/g/benchgraffiti/havlak/havlak3.go
  56.3   56.3 Total MB (flat / cumulative)
...
   1.9    1.9  268:     nonBackPreds := make([]map[int]bool, size)
   5.8    5.8  269:     backPreds := make([][]int, size)
     .      .  270:
   1.9    1.9  271:     number := make([]int, size)
   1.9    1.9  272:     header := make([]int, size, size)
   1.9    1.9  273:     types := make([]int, size, size)
   1.9    1.9  274:     last := make([]int, size, size)
   1.9    1.9  275:     nodes := make([]*UnionFindNode, size, size)
     .      .  276:
     .      .  277:     for i := 0; i < size; i++ {
   9.5    9.5  278:             nodes[i] = new(UnionFindNode)
     .      .  279:     }
...
     .      .  286:     for i, bb := range cfgraph.Blocks {
     .      .  287:             number[bb.Name] = unvisited
  29.5   29.5  288:             nonBackPreds[i] = make(map[int]bool)
     .      .  289:     }
...

可以發現 L288 佔用了 29.5M 內存。用-inuse_objects來觀察,可以看到分配次數:

$ go tool pprof --inuse_objects havlak3 havlak3.mprof
Adjusting heap profiles for 1-in-524288 sampling rate
Welcome to pprof!  For help, type 'help'.
(pprof) list FindLoops
Total: 1763108 objects
ROUTINE ====================== main.FindLoops in /home/rsc/g/benchgraffiti/havlak/havlak3.go
720903 720903 Total objects (flat / cumulative)
...
     .      .  277:     for i := 0; i < size; i++ {
311296 311296  278:             nodes[i] = new(UnionFindNode)
     .      .  279:     }
     .      .  280:
     .      .  281:     // Step a:
     .      .  282:     //   - initialize all nodes as unvisited.
     .      .  283:     //   - depth-first traversal and numbering.
     .      .  284:     //   - unreached BB's are marked as dead.
     .      .  285:     //
     .      .  286:     for i, bb := range cfgraph.Blocks {
     .      .  287:             number[bb.Name] = unvisited
409600 409600  288:             nonBackPreds[i] = make(map[int]bool)
     .      .  289:     }
...
(pprof)

分析 GC

你可以通過 CPU Profile 來分析 GC:

(pprof) top10
Total: 1173 samples
     205  17.5%  17.5%     1083  92.3% main.FindLoops
     138  11.8%  29.2%      215  18.3% scanblock
      88   7.5%  36.7%       96   8.2% sweepspan
      76   6.5%  43.2%      597  50.9% runtime.mallocgc
      75   6.4%  49.6%       78   6.6% runtime.settype_flush
      74   6.3%  55.9%       75   6.4% flushptrbuf
      64   5.5%  61.4%       64   5.5% runtime.memmove
      63   5.4%  66.8%      524  44.7% runtime.growslice
      51   4.3%  71.1%       51   4.3% main.DFS
      50   4.3%  75.4%      146  12.4% runtime.MCache_Alloc
(pprof)

可以看到runtime.mallocgc的調用次數佔了 50.9%。

查看系統爲何進行垃圾收集的另一種方法是查看導致收集的分配,這些分配在 mallocgc 中花費了大部分時間。使用--nodefraction=0.1去掉佔比小於 10% 的結果:

可以看到main.FindLoops導致了大多數 GC。

在線 Profile

如果你是一個 Web 應用,你可以使用 net/http/pprof 來添加一個 Handler,訪問http://<host>:<port>/debug/pprof/可以得到功能列表:

/debug/pprof/

Types of profiles available:
Count Profile
8   allocs
0   block
0   cmdline
10 goroutine
8   heap
0   mutex
0   profile
19 threadcreate
0   trace
...

然後你可以通過這樣來用go tool pprof <url>來分析,比如:

# 分析 CPU
go tool pprof http://localhost:9090/debug/pprof/profile
# 打開網頁分析 heap
go tool pprof -http=0.0.0.0:4231 http://localhost:9090/debug/pprof/heap

在生產中,你需要對/debug/pprof/*做 HTTP BasicAuth 保護(很簡單,一個響應頭和請求頭罷了),那麼你去抓取數據分析的時候得這樣:

go tool pprof http://<user>:<password>@localhost:9090/debug/pprof/profile

轉自: chanjarster.github.io/post/go/pprof-explained/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/aAtbKJGnyMG477dwsA4nGg