自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Go 程序崩了?煎魚(yú)教你用 PProf 工具來(lái)救火!

開(kāi)發(fā) 開(kāi)發(fā)工具
在本文中我們?cè)敿?xì)的介紹了 Go 語(yǔ)言中 pprof 的使用,針對(duì)一些常用的套件均進(jìn)行了說(shuō)明。而 pprof 在我們平時(shí)的性能剖析,問(wèn)題排查上都占據(jù)著非常重要的角色。

 [[396774]]

本文轉(zhuǎn)載自微信公眾號(hào)「腦子進(jìn)煎魚(yú)了」,作者陳煎魚(yú)。轉(zhuǎn)載本文請(qǐng)聯(lián)系腦子進(jìn)煎魚(yú)了公眾號(hào)。

前言

應(yīng)用程序在運(yùn)行時(shí),總是會(huì)出現(xiàn)一些你所意料不到的問(wèn)題,像是跑著跑著突然報(bào)警,監(jiān)控提示你進(jìn)程 CPU 使用率過(guò)高、內(nèi)存占用不斷增大(疑似泄露)、臨時(shí)內(nèi)存大量申請(qǐng)后長(zhǎng)時(shí)間內(nèi)不滑,又或是 Goroutine 泄露、出現(xiàn) Goroutine 數(shù)量暴漲,并且持續(xù)保持,甚至是莫名其妙在某次迭代發(fā)布后的數(shù)小時(shí)內(nèi)出現(xiàn)了應(yīng)用程序無(wú)法提供服務(wù)的問(wèn)題...

這發(fā)生起來(lái)的話,是多么的讓人感到擔(dān)憂,那么除了在我們平時(shí)要做好各類防護(hù)以外,在問(wèn)題正在發(fā)生時(shí),我們又有什么辦法排查呢,因此在這個(gè)章節(jié),我們將介紹排查辦法之一,也就是 Go 語(yǔ)言的性能剖析大殺器 PProf 工具鏈,它是 Go 語(yǔ)言中必知必會(huì)的技能點(diǎn)。

PProf 是什么

在 Go 語(yǔ)言中,PProf 是用于可視化和分析性能分析數(shù)據(jù)的工具,PProf 以 profile.proto 讀取分析樣本的集合,并生成報(bào)告以可視化并幫助分析數(shù)據(jù)(支持文本和圖形報(bào)告)。

而剛剛提到的 profile.proto 是一個(gè) Protobuf v3 的描述文件,它描述了一組 callstack 和 symbolization 信息, 作用是統(tǒng)計(jì)分析的一組采樣的調(diào)用棧,是很常見(jiàn)的 stacktrace 配置文件格式。

有哪幾種采樣方式

  • runtime/pprof:采集程序(非 Server)的指定區(qū)塊的運(yùn)行數(shù)據(jù)進(jìn)行分析。
  • net/http/pprof:基于HTTP Server運(yùn)行,并且可以采集運(yùn)行時(shí)數(shù)據(jù)進(jìn)行分析。
  • go test:通過(guò)運(yùn)行測(cè)試用例,并指定所需標(biāo)識(shí)來(lái)進(jìn)行采集。

支持什么使用模式

  • Report generation:報(bào)告生成。
  • Interactive terminal use:交互式終端使用。
  • Web interface:Web 界面。

可以做什么

  • CPU Profiling:CPU 分析,按照一定的頻率采集所監(jiān)聽(tīng)的應(yīng)用程序 CPU(含寄存器)的使用情況,可確定應(yīng)用程序在主動(dòng)消耗 CPU 周期時(shí)花費(fèi)時(shí)間的位置。
  • Memory Profiling:內(nèi)存分析,在應(yīng)用程序進(jìn)行堆分配時(shí)記錄堆棧跟蹤,用于監(jiān)視當(dāng)前和歷史內(nèi)存使用情況,以及檢查內(nèi)存泄漏。
  • Block Profiling:阻塞分析,記錄Goroutine阻塞等待同步(包括定時(shí)器通道)的位置,默認(rèn)不開(kāi)啟,需要調(diào)用runtime.SetBlockProfileRate進(jìn)行設(shè)置。
  • Mutex Profiling:互斥鎖分析,報(bào)告互斥鎖的競(jìng)爭(zhēng)情況,默認(rèn)不開(kāi)啟,需要調(diào)用runtime.SetMutexProfileFraction進(jìn)行設(shè)置。
  • Goroutine Profiling:Goroutine 分析,可以對(duì)當(dāng)前應(yīng)用程序正在運(yùn)行的 Goroutine 進(jìn)行堆棧跟蹤和分析。

其中像是 Goroutine Profiling 這項(xiàng)功能會(huì)在實(shí)際排查中會(huì)經(jīng)常用到。

因?yàn)楹芏鄦?wèn)題出現(xiàn)時(shí)的表象就是 Goroutine 暴增,而這時(shí)候我們要做的事情之一就是查看應(yīng)用程序中的 Goroutine 正在做什么事情,因?yàn)槭裁醋枞?,然后再進(jìn)行下一步。

介紹和使用

一個(gè)簡(jiǎn)單的例子

我們新建一個(gè) main.go 文件,用于后續(xù)的應(yīng)用程序分析和示例展示,寫(xiě)入如下代碼:

  1. var datas []string 
  2.  
  3. func main() { 
  4.  go func() { 
  5.   for { 
  6.    log.Printf("len: %d"Add("go-programming-tour-book")) 
  7.    time.Sleep(time.Millisecond * 10) 
  8.   } 
  9.  }() 
  10.  
  11.  _ = http.ListenAndServe("0.0.0.0:6060", nil) 
  12.  
  13. func Add(str string) int { 
  14.  data := []byte(str) 
  15.  datas = append(datas, string(data)) 
  16.  return len(datas) 

接下來(lái)最重要的一步,就是在 import 中添加 _ "net/http/pprof" 的引用,如下:

  1. import ( 
  2.  _ "net/http/pprof" 
  3.  ... 

接下來(lái)我們運(yùn)行這個(gè)程序,訪問(wèn) http://127.0.0.1:6060/debug/pprof/ 地址,檢查是否正常響應(yīng)。

通過(guò)瀏覽器訪問(wèn)

第一種方式,我們可以直接通過(guò)瀏覽器,進(jìn)行查看,那么在第一步我們可以先查看總覽頁(yè)面,也就是訪問(wèn) http://127.0.0.1:6060/debug/pprof/,如下:

  1. /debug/pprof/ 
  2.  
  3. Types of profiles available: 
  4. Count Profile 
  5. 3 allocs 
  6. 0 block 
  7. 0 cmdline 
  8. 8 goroutine 
  9. 3 heap 
  10. 0 mutex 
  11. 0 profile 
  12. 11 threadcreate 
  13. 0 trace 
  14. full goroutine stack dump 
  • allocs:查看過(guò)去所有內(nèi)存分配的樣本,訪問(wèn)路徑為$HOST/debug/pprof/allocs。
  • block:查看導(dǎo)致阻塞同步的堆棧跟蹤,訪問(wèn)路徑為$HOST/debug/pprof/block。
  • cmdline:當(dāng)前程序的命令行的完整調(diào)用路徑。
  • goroutine:查看當(dāng)前所有運(yùn)行的 goroutines 堆棧跟蹤,訪問(wèn)路徑為$HOST/debug/pprof/goroutine。
  • heap:查看活動(dòng)對(duì)象的內(nèi)存分配情況, 訪問(wèn)路徑為$HOST/debug/pprof/heap。
  • mutex:查看導(dǎo)致互斥鎖的競(jìng)爭(zhēng)持有者的堆棧跟蹤,訪問(wèn)路徑為$HOST/debug/pprof/mutex。
  • profile:默認(rèn)進(jìn)行 30s 的 CPU Profiling,得到一個(gè)分析用的 profile 文件,訪問(wèn)路徑為$HOST/debug/pprof/profile。
  • threadcreate:查看創(chuàng)建新OS線程的堆棧跟蹤,訪問(wèn)路徑為$HOST/debug/pprof/threadcreate。

如果你在對(duì)應(yīng)的訪問(wèn)路徑上新增 ?debug=1 的話,就可以直接在瀏覽器訪問(wèn),如下:

debug 模式

若不新增 debug 參數(shù),那么將會(huì)直接下載對(duì)應(yīng)的profile文件。

再展開(kāi)來(lái)講,在部署環(huán)境中,我們?yōu)榱司W(wǎng)絡(luò)安全,通常不會(huì)直接對(duì)外網(wǎng)暴露 pprof 的相關(guān)端口,因此會(huì)通過(guò) curl、wget 等方式進(jìn)行 profile 文件的間接拉取。

另外還有一點(diǎn)需要注意,debug 的訪問(wèn)方式是具有時(shí)效性的,在實(shí)際場(chǎng)景中,我們常常需要及時(shí)將當(dāng)前狀態(tài)下的 profile 文件給存儲(chǔ)下來(lái),便于二次分析。

通過(guò)交互式終端使用

第二種方式,我們可以直接通過(guò)命令行,來(lái)完成對(duì)正在運(yùn)行的應(yīng)用程序 pprof 的抓取和分析。

CPU Profiling

  1. $ go tool pprof http://localhost:6060/debug/pprof/profile\?seconds\=60 
  2. Fetching profile over HTTP from http://localhost:6060/debug/pprof/profile?seconds=60 
  3. Saved profile in /Users/eddycjy/pprof/pprof.samples.cpu.002.pb.gz 
  4. Type: cpu 
  5. Duration: 1mins, Total samples = 37.25s (61.97%) 
  6. Entering interactive mode (type "help" for commands, "o" for options) 
  7. (pprof) 

執(zhí)行該命令后,需等待 60 秒(可調(diào)整 seconds 的值),pprof 會(huì)進(jìn)行 CPU Profiling,結(jié)束后將默認(rèn)進(jìn)入 pprof 的命令行交互式模式,可以對(duì)分析的結(jié)果進(jìn)行查看或?qū)С?。另外如果你所啟?dòng)的 HTTP Server 是 TLS 的方式,那么在調(diào)用 go tool pprof 時(shí),需要將調(diào)用路徑改為:go tool pprof https+insecure://localhost:6060/debug/pprof/profile\?seconds\=60。

接下來(lái)我們輸入查詢命令 top10 ,以此查看對(duì)應(yīng)資源開(kāi)銷(例如:CPU 就是執(zhí)行耗時(shí)/開(kāi)銷、Memory 就是內(nèi)存占用大小)排名前十的函數(shù),如下:

  1. (pprof) top10 
  2. Showing nodes accounting for 36.23s, 97.26% of 37.25s total 
  3. Dropped 80 nodes (cum <= 0.19s) 
  4. Showing top 10 nodes out of 34 
  5.       flat  flat%   sum%        cum   cum%  Name 
  6.     32.63s 87.60% 87.60%     32.70s 87.79%  syscall.syscall 
  7.      0.87s  2.34% 89.93%      0.88s  2.36%  runtime.stringtoslicebyte 
  8.      0.69s  1.85% 91.79%      0.69s  1.85%  runtime.memmove 
  9.      0.52s  1.40% 93.18%      0.52s  1.40%  runtime.nanotime 
  10.      ... 
  11. (pprof)  
  • flat:函數(shù)自身的運(yùn)行耗時(shí)。
  • flat%:函數(shù)自身在 CPU 運(yùn)行耗時(shí)總比例。
  • sum%:函數(shù)自身累積使用 CPU 總比例。
  • cum:函數(shù)自身及其調(diào)用函數(shù)的運(yùn)行總耗時(shí)。
  • cum%:函數(shù)自身及其調(diào)用函數(shù)的運(yùn)行耗時(shí)總比例。
  • Name:函數(shù)名。

在大多數(shù)的情況下,我們可以通過(guò)這五列得出一個(gè)應(yīng)用程序的運(yùn)行情況,知道當(dāng)前是什么函數(shù),正在做什么事情,占用了多少資源,誰(shuí)又是占用資源的大頭,以此來(lái)得到一個(gè)初步的分析方向。

另外在交互命令行中,pprof 還支持了大量的其它命令,具體可執(zhí)行 pprof help 查看幫助說(shuō)明。

Heap Profiling

  1. $ go tool pprof http://localhost:6060/debug/pprof/heap 
  2. Fetching profile over HTTP from http://localhost:6060/debug/pprof/heap 
  3. Saved profile in /Users/eddycjy/pprof/pprof.alloc_objects.alloc_space.inuse_objects.inuse_space.011.pb.gz 
  4. Type: inuse_space 
  5. Entering interactive mode (type "help" for commands, "o" for options) 
  6. (pprof) 

執(zhí)行該命令后,能夠很快的拉取到其結(jié)果,因?yàn)樗恍枰?CPU Profiling 做采樣等待,這里需要注意的一點(diǎn)是 Type 這一個(gè)選項(xiàng),你可以看到它默認(rèn)顯示的是 inuse_space,實(shí)際上可以針對(duì)多種內(nèi)存概況進(jìn)行分析,常用的類別如下:

  • inuse_space:分析應(yīng)用程序的常駐內(nèi)存占用情況。
  1. $ go tool pprof -inuse_space http://localhost:6060/debug/pprof/heap 
  2. (pprof) top 
  3. Showing nodes accounting for 4.01GB, 100% of 4.01GB total 
  4.       flat  flat%   sum%        cum   cum% 
  5.     4.01GB   100%   100%     4.01GB   100%  main.Add 
  6.          0     0%   100%     4.01GB   100%  main.main.func1 
  • alloc_objects:分析應(yīng)用程序的內(nèi)存臨時(shí)分配情況。
  1. $ go tool pprof -alloc_objects http://localhost:6060/debug/pprof/heap 
  2. (pprof) top 
  3. Showing nodes accounting for 215552550, 100% of 215560746 total 
  4. Dropped 14 nodes (cum <= 1077803) 
  5.       flat  flat%   sum%        cum   cum% 
  6.   86510197 40.13% 40.13%   86510197 40.13%  main.Add 
  7.   85984544 39.89% 80.02%   85984544 39.89%  fmt.Sprintf 
  8.   43057809 19.97%   100%  215552550   100%  main.main.func1 
  9.          0     0%   100%   85984544 39.89%  log.Printf 

另外還有 inuse_objects 和 alloc_space 類別,分別對(duì)應(yīng)查看每個(gè)函數(shù)所分別的對(duì)象數(shù)量和查看分配的內(nèi)存空間大小,具體可根據(jù)情況選用。

Goroutine Profiling

  1. $ go tool pprof http://localhost:6060/debug/pprof/goroutine 
  2. Fetching profile over HTTP from http://localhost:6060/debug/pprof/goroutine 
  3. Saved profile in /Users/eddycjy/pprof/pprof.goroutine.003.pb.gz 
  4. Type: goroutine 
  5. Entering interactive mode (type "help" for commands, "o" for options) 
  6. (pprof)  

在查看 goroutine 時(shí),我們可以使用traces命令,這個(gè)命令會(huì)打印出對(duì)應(yīng)的所有調(diào)用棧,以及指標(biāo)信息,可以讓我們很便捷的查看到整個(gè)調(diào)用鏈路有什么,分別在哪里使用了多少個(gè) goroutine,并且能夠通過(guò)分析查看到誰(shuí)才是真正的調(diào)用方,輸出結(jié)果如下:

  1. (pprof) traces 
  2. Type: goroutine 
  3. -----------+------------------------------------------------------- 
  4.          2   runtime.gopark 
  5.              runtime.netpollblock 
  6.              internal/poll.runtime_pollWait 
  7.              ... 
  8. -----------+------------------------------------------------------- 
  9.          1   runtime.gopark 
  10.              runtime.netpollblock 
  11.              ... 
  12.              net/http.ListenAndServe 
  13.              main.main 
  14.              runtime.main 

在調(diào)用棧上來(lái)講,其展示順序是自下而上的,也就是 runtime.main 方法調(diào)用了 main.main 方法,main.main 方法又調(diào)用了 net/http.ListenAndServe 方法,這里對(duì)應(yīng)的也就是我們所使用的示例代碼了,排查起來(lái)會(huì)非常方便。

每個(gè)調(diào)用堆棧信息用 ----------- 分割,函數(shù)方法前的就是指標(biāo)數(shù)據(jù),像 Goroutine Profiling 展示是就是該方法占用的 goroutine 的數(shù)量。而 Heap Profiling 展示的就是占用的內(nèi)存大小,如下:

  1. $ go tool pprof http://localhost:6060/debug/pprof/heap 
  2. ... 
  3. Type: inuse_space 
  4. Entering interactive mode (type "help" for commands, "o" for options) 
  5. (pprof) traces 
  6. Type: inuse_space 
  7. -----------+------------------------------------------------------- 
  8.      bytes:  13.55MB 
  9.    13.55MB   main.Add 
  10.              main.main.func1 
  11. -----------+------------------------------------------------------- 

實(shí)際上 pprof 中的所有功能都會(huì)根據(jù)不同的 Profile 類型展示不同的對(duì)應(yīng)結(jié)果。

Mutex Profiling

怎么樣的情況下會(huì)造成阻塞呢,一般有如下方式:調(diào)用 chan(通道)、調(diào)用 sync.Mutex (同步鎖)、調(diào)用 time.Sleep() 等等。那么為了驗(yàn)證互斥鎖的競(jìng)爭(zhēng)持有者的堆棧跟蹤,我們可以根據(jù)以上的 sync.Mutex 方式,來(lái)調(diào)整先前的示例代碼,代碼如下:

  1. func init() { 
  2.  runtime.SetMutexProfileFraction(1) 
  3.  
  4. func main() { 
  5.  var m sync.Mutex 
  6.  var datas = make(map[int]struct{}) 
  7.  for i := 0; i < 999; i++ { 
  8.   go func(i int) { 
  9.    m.Lock() 
  10.    defer m.Unlock() 
  11.    datas[i] = struct{}{} 
  12.   }(i) 
  13.  } 
  14.  
  15.  _ = http.ListenAndServe(":6061", nil) 

需要特別注意的是 runtime.SetMutexProfileFraction 語(yǔ)句,如果未來(lái)希望進(jìn)行互斥鎖的采集,那么需要通過(guò)調(diào)用該方法來(lái)設(shè)置采集頻率,若不設(shè)置或沒(méi)有設(shè)置大于 0 的數(shù)值,默認(rèn)是不進(jìn)行采集的。

接下來(lái)我們進(jìn)行調(diào)用 go tool pprof 進(jìn)行分析,如下:

  1. $ go tool pprof http://localhost:6061/debug/pprof/mutex 
  2. Fetching profile over HTTP from http://localhost:6061/debug/pprof/mutex 
  3. Saved profile in /Users/eddycjy/pprof/pprof.contentions.delay.010.pb.gz 
  4. Type: delay 
  5. Entering interactive mode (type "help" for commands, "o" for options) 

我們查看調(diào)用 top 命令,查看互斥量的排名:

  1. (pprof) top 
  2. Showing nodes accounting for 653.79us, 100% of 653.79us total 
  3.       flat  flat%   sum%        cum   cum% 
  4.   653.79us   100%   100%   653.79us   100%  sync.(*Mutex).Unlock 
  5.          0     0%   100%   653.79us   100%  main.main.func1 

接下來(lái)我們可以調(diào)用 list 命令,看到指定函數(shù)的代碼情況(包含特定的指標(biāo)信息,例如:耗時(shí)),若函數(shù)名不明確,默認(rèn)會(huì)對(duì)函數(shù)名進(jìn)行模糊匹配,如下:

  1. (pprof) list main 
  2. Total: 653.79us 
  3. ROUTINE ======================== main.main.func1 in /eddycjy/main.go 
  4.          0   653.79us (flat, cum)   100% of Total 
  5.          .          .     40:  go func(i int) { 
  6.          .          .     41:   m.Lock() 
  7.          .          .     42:   defer m.Unlock() 
  8.          .          .     43: 
  9.          .          .     44:   datas[i] = struct{}{} 
  10.          .   653.79us     45:  }(i) 
  11.          .          .     46: } 
  12.          .          .     47: 
  13.          .          .     48: err := http.ListenAndServe(":6061", nil) 
  14.          .          .     49: if err != nil { 
  15.          .          .     50:  log.Fatalf("http.ListenAndServe err: %v", err) 
  16. (pprof)  

我們可以在輸出的分析中比較準(zhǔn)確的看到引起互斥鎖的函數(shù)在哪里,鎖開(kāi)銷在哪里,在本例中是第 45 行。

Block Profiling

與 Mutex 的 runtime.SetMutexProfileFraction 相似,Block 也需要調(diào)用 runtime.SetBlockProfileRate() 進(jìn)行采集量的設(shè)置,否則默認(rèn)關(guān)閉,若設(shè)置的值小于等于 0 也會(huì)認(rèn)為是關(guān)閉。

與上小節(jié) Mutex 相比,主體代碼不變,僅是新增 runtime.SetBlockProfileRate() 的調(diào)用,如下:

  1. func init() { 
  2.  runtime.SetBlockProfileRate(1) 
  3.  ... 

我們查看調(diào)用 top 命令,查看阻塞情況的排名:

  1. $ go tool pprof http://localhost:6061/debug/pprof/block 
  2. Fetching profile over HTTP from http://localhost:6061/debug/pprof/block 
  3. Saved profile in /Users/eddycjy/pprof/pprof.contentions.delay.017.pb.gz 
  4. Type: delay 
  5. Entering interactive mode (type "help" for commands, "o" for options) 
  6. (pprof) top 
  7. Showing nodes accounting for 74.54ms, 100% of 74.54ms total 
  8.       flat  flat%   sum%        cum   cum% 
  9.    74.54ms   100%   100%    74.54ms   100%  sync.(*Mutex).Lock 
  10.          0     0%   100%    74.54ms   100%  main.main.func1 

同樣的,我們也可以調(diào)用 list 命令查看具體的阻塞情況,執(zhí)行方式和排查模式與先前概述的一致。

查看可視化界面

接下來(lái)我們繼續(xù)使用前面的示例程序,將其重新運(yùn)行起來(lái),然后在其它窗口執(zhí)行下述命令:

  1. $ wget http://127.0.0.1:6060/debug/pprof/profile    

默認(rèn)需要等待 30 秒,執(zhí)行完畢后可在當(dāng)前目錄下發(fā)現(xiàn)采集的文件 profile,針對(duì)可視化界面我們有兩種方式可進(jìn)行下一步分析:

方法一(推薦):該命令將在所指定的端口號(hào)運(yùn)行一個(gè) PProf 的分析用的站點(diǎn)。

  1. $ go tool pprof -http=:6001 profile  

方法二:通過(guò) web 命令將以 svg 的文件格式寫(xiě)入圖形,然后在 Web 瀏覽器中將其打開(kāi)。

  1. $ go tool pprof profile 
  2. Type: cpu 
  3. Time: Feb 1, 2020 at 12:09pm (CST) 
  4. Duration: 30s, Total samples = 60ms (  0.2%) 
  5. Entering interactive mode (type "help" for commands, "o" for options) 
  6. (pprof) web 

如果出現(xiàn)錯(cuò)誤提示 Could not execute dot; may need to install graphviz.,那么意味著你需要安裝 graphviz組件。

另外方法一所運(yùn)行的站點(diǎn),實(shí)際上包含了方法二的內(nèi)容(svg圖片),并且更靈活,因此非特殊情況,我們會(huì)直接使用方法一的方式運(yùn)行站點(diǎn)來(lái)做觀察和分析。

剖析內(nèi)容

通過(guò) PProf 所提供的可視化界面,我們能夠更方便、更直觀的看到 Go 應(yīng)用程序的調(diào)用鏈、使用情況等。另外在 View 菜單欄中,PProf 還支持多種分析方式的切換,如下:

view 菜單欄

接下來(lái)我們將基于 CPU Profiling 所抓取的 Profile 進(jìn)行一一介紹,而其它 Profile 類型的分析模式也是互通的,只要我們了解了一種,其余的也就會(huì)了。

Top

top 欄目

該視圖與前面所講解的 top 子命令的作用和含義是一樣的,因此不再贅述。

Graph

graph 欄目

該視圖展示的為整體的函數(shù)調(diào)用流程,框越大、線越粗、框顏色越鮮艷(紅色)就代表它占用的時(shí)間越久,開(kāi)銷越大。相反若框顏色越淡,越小則代表在整體的函數(shù)調(diào)用流程中,它的開(kāi)銷是相對(duì)較小的。

因此我們可以用此視圖去分析誰(shuí)才是開(kāi)銷大頭,它又是因?yàn)槭裁凑{(diào)用流程而被調(diào)用的。

Peek

peek 欄目

此視圖相較于 Top 視圖,增加了所屬的上下文信息的展示,也就是函數(shù)的輸出調(diào)用者/被調(diào)用者。

Source

source 欄目

該視圖主要是增加了面向源代碼的追蹤和分析,可以看到其開(kāi)銷主要消耗在哪里。

Flame Graph

flame graph 概覽

Flame Graph(火焰圖)它是可動(dòng)態(tài)的,調(diào)用順序由上到下(A -> B -> C -> D),每一塊代表一個(gè)函數(shù)、顏色越鮮艷(紅)、區(qū)塊越大代表占用 CPU 的時(shí)間更長(zhǎng)。同時(shí)它也支持點(diǎn)擊塊深入進(jìn)行分析。

我們選擇頁(yè)面上的 main.main.func1 區(qū)塊,將會(huì)進(jìn)入到其屬下的下一層級(jí),如下:

進(jìn)一步查看 flame graph

這樣子我們就可以根據(jù)不同函數(shù)的多維度層級(jí)進(jìn)行分析,能夠更好的觀察其流轉(zhuǎn)并發(fā)現(xiàn)問(wèn)題。

通過(guò)測(cè)試用例做剖析

在上述章節(jié)中,我們是通過(guò)在應(yīng)用程序中埋入方法進(jìn)行采集的,那么還有一種方式,能夠更精準(zhǔn)的剖析到你所想要分析的流程或函數(shù)。

首先我們將先前所編寫(xiě)的 Add 方法挪到獨(dú)立的 package 中,命名為 add.go 文件,然后新建 add_test.go 文件,寫(xiě)入如下測(cè)試用例代碼:

  1. func TestAdd(t *testing.T) { 
  2.  _ = Add("go-programming-tour-book"
  3.  
  4. func BenchmarkAdd(b *testing.B) { 
  5.  for i := 0; i < b.N; i++ { 
  6.   Add("go-programming-tour-book"
  7.  } 

在完成代碼編寫(xiě)后,我們回到命令行窗口執(zhí)行如下采集命令:

  1. $ go test -bench=. -cpuprofile=cpu.profile 

執(zhí)行完畢后會(huì)在當(dāng)前命令生成 cpu.profile 文件,然后只需執(zhí)行 go tool pprof 命令就可以進(jìn)行查看了,如下圖:

cpu profile

另外除了對(duì) CPU 進(jìn)行剖析以外,我們還可以調(diào)整選項(xiàng),對(duì)內(nèi)存情況進(jìn)行分析,如下采集命令:

  1. $ go test -bench=. -memprofile=mem.profile 

接下來(lái)和上面一樣,執(zhí)行 go tool pprof 命令進(jìn)行查看,如下圖:

進(jìn)一步查看

通過(guò) Lookup 寫(xiě)入文件做剖析

除了注入 http handler 和 go test 以外,我們還可以在程序中通過(guò) pprof 所提供的 Lookup 方法來(lái)進(jìn)行相關(guān)內(nèi)容的采集和調(diào)用。

其一共支持六種類型,分別是:

  • goroutine。
  • threadcreate。
  • heap。
  • block。
  • mutex。

具體代碼如下:

  1. type LookupType int8 
  2.  
  3. const ( 
  4.  LookupGoroutine LookupType = iota 
  5.  LookupThreadcreate 
  6.  LookupHeap 
  7.  LookupAllocs 
  8.  LookupBlock 
  9.  LookupMutex 
  10.  
  11. func pprofLookup(lookupType LookupType, w io.Writer) error { 
  12.  var err error 
  13.  switch lookupType { 
  14.  case LookupGoroutine: 
  15.   p := pprof.Lookup("goroutine"
  16.   err = p.WriteTo(w, 2) 
  17.  case LookupThreadcreate: 
  18.   p := pprof.Lookup("threadcreate"
  19.   err = p.WriteTo(w, 2) 
  20.  case LookupHeap: 
  21.   p := pprof.Lookup("heap"
  22.   err = p.WriteTo(w, 2) 
  23.  case LookupAllocs: 
  24.   p := pprof.Lookup("allocs"
  25.   err = p.WriteTo(w, 2) 
  26.  case LookupBlock: 
  27.   p := pprof.Lookup("block"
  28.   err = p.WriteTo(w, 2) 
  29.  case LookupMutex: 
  30.   p := pprof.Lookup("mutex"
  31.   err = p.WriteTo(w, 2) 
  32.  } 
  33.  
  34.  return err 

接下來(lái)我們只需要對(duì)該方法進(jìn)行調(diào)用就好了,其提供了 io.Writer 接口,也就是只要實(shí)現(xiàn)了對(duì)應(yīng)的 Write 方法,我們可以將其寫(xiě)到任何支持地方去,如下:

  1. ... 
  2. func init() { 
  3.  runtime.SetMutexProfileFraction(1) 
  4.  runtime.SetBlockProfileRate(1) 
  5.  
  6. func main() { 
  7.  http.HandleFunc("/lookup/heap", func(w http.ResponseWriter, r *http.Request) { 
  8.   _ = pprofLookup(LookupHeap, os.Stdout) 
  9.  }) 
  10.  http.HandleFunc("/lookup/threadcreate", func(w http.ResponseWriter, r *http.Request) { 
  11.   _ = pprofLookup(LookupThreadcreate, os.Stdout) 
  12.  }) 
  13.  http.HandleFunc("/lookup/block", func(w http.ResponseWriter, r *http.Request) { 
  14.   _ = pprofLookup(LookupBlock, os.Stdout) 
  15.  }) 
  16.  http.HandleFunc("/lookup/goroutine", func(w http.ResponseWriter, r *http.Request) { 
  17.   _ = pprofLookup(LookupGoroutine, os.Stdout) 
  18.  }) 
  19.  _ = http.ListenAndServe("0.0.0.0:6060", nil) 

在上述代碼中,我們將采集結(jié)果寫(xiě)入到了控制臺(tái)上,我們可以進(jìn)行一下驗(yàn)證,調(diào)用 http://127.0.0.1:6060/lookup/heap,控制臺(tái)輸出結(jié)果如下:

  1. $ go run main.go 
  2. heap profile: 0: 0 [0: 0] @ heap/1048576 
  3.  
  4. # runtime.MemStats 
  5. # Alloc = 180632 
  6. # TotalAlloc = 180632 
  7. # Sys = 69928960 
  8. # Lookups = 0 
  9. ... 

為什么要初始化net/http/pprof

在我們的例子中,你會(huì)發(fā)現(xiàn)我們?cè)谝蒙蠈?duì) net/http/pprof 包進(jìn)行了默認(rèn)的初始化(也就是 _),如果你曾經(jīng)漏了,或者沒(méi)加,你會(huì)發(fā)現(xiàn)壓根調(diào)用不了 pprof 的相關(guān)接口,這是為什么呢,我們一起看看下面該包的初始化方法,如下:

  1. func init() { 
  2.  http.HandleFunc("/debug/pprof/"Index
  3.  http.HandleFunc("/debug/pprof/cmdline", Cmdline) 
  4.  http.HandleFunc("/debug/pprof/profile", Profile) 
  5.  http.HandleFunc("/debug/pprof/symbol", Symbol) 
  6.  http.HandleFunc("/debug/pprof/trace", Trace) 

實(shí)際上 net/http/pprof 會(huì)在初始化函數(shù)中對(duì)標(biāo)準(zhǔn)庫(kù)中 net/http 所默認(rèn)提供的 DefaultServeMux 進(jìn)行路由注冊(cè),源碼如下:

  1. var DefaultServeMux = &defaultServeMux 
  2. var defaultServeMux ServeMux 
  3.  
  4. func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { 
  5.  DefaultServeMux.HandleFunc(pattern, handler) 

而我們?cè)诶又惺褂玫?HTTP Server,也是使用的標(biāo)準(zhǔn)庫(kù)中默認(rèn)提供的,因此便完美的結(jié)合在了一起,這也恰好也是最小示例的模式。

這時(shí)候你可能會(huì)注意到另外一個(gè)問(wèn)題,那就是我們的實(shí)際項(xiàng)目中,都是有相對(duì)獨(dú)立的 ServeMux 的,這時(shí)候我們只要仿照著將 pprof 對(duì)應(yīng)的路由注冊(cè)進(jìn)去就好了,如下:

  1. mux := http.NewServeMux() 
  2. mux.HandleFunc("/debug/pprof/", pprof.Index
  3. mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 
  4. mux.HandleFunc("/debug/pprof/profile", pprof.Profile) 
  5. mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 
  6. mux.HandleFunc("/debug/pprof/trace", pprof.Trace) 

總結(jié)

在本文中我們?cè)敿?xì)的介紹了 Go 語(yǔ)言中 pprof 的使用,針對(duì)一些常用的套件均進(jìn)行了說(shuō)明。而 pprof 在我們平時(shí)的性能剖析,問(wèn)題排查上都占據(jù)著非常重要的角色。

在日常只需要根據(jù)合理的排查思路,相信你一定能夠根據(jù)在 pprof 中的蛛絲馬跡,解決那些或大或小的問(wèn)題,實(shí)現(xiàn)準(zhǔn)點(diǎn)下班!

 

責(zé)任編輯:武曉燕 來(lái)源: 腦子進(jìn)煎魚(yú)了
相關(guān)推薦

2022-02-07 08:55:57

Go程序代碼

2022-06-27 11:20:13

工具內(nèi)存GO

2018-01-02 16:48:58

Python 微信安卓

2021-05-18 14:42:55

PythonMySQL

2019-01-24 09:00:00

PythonAutoML機(jī)器學(xué)習(xí)

2015-03-23 12:33:28

2014-07-22 10:19:19

NeoBundle

2015-04-22 11:29:45

PythonPython創(chuàng)建瀑布圖

2023-10-27 11:38:09

PythonWord

2019-09-05 10:07:23

ZAODeepfakes換臉

2023-08-03 08:51:07

2020-03-25 14:40:45

語(yǔ)言編程語(yǔ)言Hello

2014-07-21 09:51:10

AndroidResflux修改應(yīng)用

2020-04-09 09:52:42

Python數(shù)據(jù)技術(shù)

2021-08-09 13:31:25

PythonExcel代碼

2022-10-19 14:30:59

2021-12-26 18:32:26

Python Heic 文件

2011-03-28 16:14:38

jQuery

2021-02-04 09:00:57

SQLDjango原生

2021-02-06 14:55:05

大數(shù)據(jù)pandas數(shù)據(jù)分析
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)