Golang pprof 使用

什麼是 Profile?

在計算機性能調試領域裏,profile 就是對應用的畫像,這裏畫像就是應用使用 CPU 和內存等情況,也就是說應用使用了多少 CPU 資源、都是哪些部分在使用、每個函數使用的比例是多少、有哪些函數在等待 CPU 資源等等。知道了這些,我們就能對應用進行規劃,也能快速定位性能瓶頸。

Golang 是一個對性能特別看重的語言,因此語言中自帶了 profile 的庫,這篇文章就要講解怎麼在 golang 中做 profile。

在 Golang 中,主要關注的應用運行情況主要包括以下幾種:

  • CPU profile:報告程序的 CPU 使用情況,按照一定頻率去採集應用程序在 CPU 和寄存器上面的數據
  • Memory profile(Heap profile):報告程序的內存使用情況
  • Block profile:報告 goroutines 不在運行狀態的情況,可以用來分析和查找死鎖等性能瓶頸
  • Goroutine profile:報告 goroutines 的使用情況,有哪些 goroutine,它們的調用關係是怎樣的

兩種收集方式

分析 profile 第一步就是怎麼獲取應用程序的運行情況數據。Golang 提供了 runtime/pprof 和 net/http/pprof 兩個庫,分別應用於兩種不同的應用。

工具型應用

如果你的應用是一次性的,運行一段時間就結束,那麼最好的辦法就是在應用退出時把 profile 的報告保存到文件中,進行分析。對於這種情況,可以使用 runtime/pprof 

pprof 封裝了很好的接口供我們使用,比如要想進行 CPU profile,可以調用 pprof.StartCPUProfile() 方法,它會對當前應用程序進行 CPU profile,並寫入到提供的參數中(w io.Writer)。要停止寫入,調用 StopCPUProfile() 即可。

去除錯誤處理只需要三行內容,一般把它們寫在 main.go 文件中,應用程序啓動之後就開始執行:

f, err := os.Create(*cpuprofile)
...
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()

應用執行結束後,就會生成一個文件,保存了我們的 CPU profile 數據。

要獲得內存的數據,直接使用 WriteHeapProfile 即可,不用 start 和 stop 這兩個步驟:

f, err := os.Create(*memprofile)
pprof.WriteHeapProfile(f)
f.Close()

服務型應用

如果你的應用是一直運行的,比如 web 應用,那麼可以使用 net/http/pprof 庫,它能夠對 Http 服務進行分析。

在 import 裏添加一行:

import _ "net/http/pprof"

在主函數中啓動服務監聽端口:

go func() {
    http.ListenAndServe(":6060", nil)
}()

訪問 /debug/pprof 即可得到下面的內容: 

/debug/pprof/

profiles:
0	block
756	goroutine
16100	heap
0	mutex
94	threadcreate

full goroutine stack dump

go tool ppof 獲取和分析 profile 數據

有了 profile 數據之後(不管是文件還是網絡請求),下一步就是要對這些數據進行分析。我們可以使用 go tool pprof 命令行工具。

在後面我們會生成調用關係圖和火焰圖,需要安裝 graphviz 軟件包,在 ubuntu 系統可以使用下面的命令:

$ sudo apt-get install -y graphviz

注意獲取的 profile 數據是動態的,要想獲得有效的數據,請保證應用處於較大的負載(比如正在運行的服務,或者通過其他工具模擬訪問壓力)。否則如果應用處於空閒狀態,得到的結果可能沒有任何意義。

我們以 CPU profile 分析爲例介紹兩種分析方法。

交互式終端

go tool pprof 最簡單的使用方式爲 go tool pprof [binary] [source]binary 是應用的二進制文件,用來解析各種符號;source 表示 profile 數據的來源,可以是本地的文件,也可以是 http 地址。比如:

 ➜  go tool pprof ./hyperkube http://172.16.3.232:10251/debug/pprof/profile
Fetching profile from http://172.16.3.232:10251/debug/pprof/profile
Please wait... (30s)
Saved profile in /home/cizixs/pprof/pprof.hyperkube.172.16.3.232:10251.samples.cpu.002.pb.gz
Entering interactive mode (type "help" for commands)
(pprof) 

這個命令會進行 CPU profile 分析,等待一段時間(默認是 30s,如果在 url 最後加上 ?seconds=60 參數可以調整採集數據的時間爲 60s)之後,我們就進入了一個交互式命令行,可以對解析的結果進行查看和導出。可以通過 help 來查看支持的命令有哪些。

一個有用的命令是 topN,它列出最耗時間的地方:

(pprof) top10
130ms of 360ms total (36.11%)
Showing top 10 nodes out of 180 (cum >= 10ms)
      flat  flat%   sum%        cum   cum%
      20ms  5.56%  5.56%      100ms 27.78%  encoding/json.(*decodeState).object
      20ms  5.56% 11.11%       20ms  5.56%  runtime.(*mspan).refillAllocCache
      20ms  5.56% 16.67%       20ms  5.56%  runtime.futex
      10ms  2.78% 19.44%       10ms  2.78%  encoding/json.(*decodeState).literalStore
      10ms  2.78% 22.22%       10ms  2.78%  encoding/json.(*decodeState).scanWhile
      10ms  2.78% 25.00%       40ms 11.11%  encoding/json.checkValid
      10ms  2.78% 27.78%       10ms  2.78%  encoding/json.simpleLetterEqualFold
      10ms  2.78% 30.56%       10ms  2.78%  encoding/json.stateBeginValue
      10ms  2.78% 33.33%       10ms  2.78%  encoding/json.stateEndValue
      10ms  2.78% 36.11%       10ms  2.78%  encoding/json.stateInString

每一行表示一個函數的信息。前兩列表示函數在 CPU 上運行的時間以及百分比;第三列是當前所有函數累加使用 CPU 的比例;第四列和第五列代表這個函數以及子函數運行所佔用的時間和比例(也被稱爲累加值 cumulative),應該大於等於前兩列的值;最後一列就是函數的名字。如果應用程序有性能問題,上面這些信息應該能告訴我們時間都花費在哪些函數的執行上了。

pprof 不僅能打印出最耗時的地方(top),還能列出函數代碼以及對應的取樣數據、彙編代碼以及對應的取樣數據。list 命令後面跟着一個正則表達式,就能查看匹配函數的代碼以及每行代碼的耗時:

 (pprof) list podFitsOnNode
Total: 120ms
ROUTINE ======================== k8s.io/kubernetes/plugin/pkg/scheduler.podFitsOnNode in /home/cizixs/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/plugin/pkg/scheduler/generic_scheduler.go
         0       20ms (flat, cum) 16.67% of Total
         .          .    230:
         .          .    231:// Checks whether node with a given name and NodeInfo satisfies all predicateFuncs.
         .          .    232:func podFitsOnNode(pod *api.Pod, meta interface{}, info *schedulercache.NodeInfo, predicateFuncs map[string]algorithm.FitPredicate) (bool, []algorithm.PredicateFailureReason, error) {
         .          .    233:	var failedPredicates []algorithm.PredicateFailureReason
         .          .    234:	for _, predicate := range predicateFuncs {
         .       20ms    235:		fit, reasons, err := predicate(pod, meta, info)
         .          .    236:		if err != nil {
         .          .    237:			err := fmt.Errorf("SchedulerPredicates failed due to %v, which is unexpected.", err)
         .          .    238:			return false, []algorithm.PredicateFailureReason{}, err
         .          .    239:		}
         .          .    240:		if !fit {

如果想要了解對應的彙編代碼,可以使用 disadm <regex> 命令。

可視化

pprof 能以各種樣式輸出數據,比如 svg、gv、callgrind、png、gif 等等。其中一個非常便利的方法是在交互式終端中輸入 web 命令,就能自動生成一個 svg 文件,並跳轉到瀏覽器打開,生成了一個函數調用圖:

這個調用圖包含了更多的信息,而且可視化的圖像能讓我們更清楚地理解整個應用程序的全貌。圖中每個方框對應一個函數,方框越大代表執行的時間越久(包括它調用的子函數執行時間,但並不是正比的關係);方框之間的箭頭代表着調用關係,箭頭上的數字代表被調用函數的執行時間。

因爲原圖比較大,這裏只截取了其中一部分,但是能明顯看到 encoding/json.(*decodeState).object 是這裏耗時比較多的地方,而且能看到它調用了哪些函數。這些信息對於定位和調優性能是非常有幫助的。如果想進一步在瀏覽器中查看源代碼和彙編代碼,可以使用 weblist 命令,和 list、disadm 的用法相同,它能夠同時顯示源代碼和彙編代碼

此外還可以輸入 pdf 命令生成一個 pdf 文件。更詳細的 pprof 使用方法可以參考 pprof --help 或者 pprof 文檔

另一個可視化的方法是直接啓動一個 http 服務:

go tool pprof -http="10.224.27.152:8081" ./hyperkube http://172.16.3.232:10251/debug/pprof/profile

在瀏覽器上訪問 10.224.27.152:8081 即可看到各種界面。

注:本文大量使用了 使用 pprof 和火焰圖調試 golang 應用 的內容,並結合了筆者平時的實踐。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章