最近計劃用三篇文章講述一下Golang
應用性能分析,本文是第一篇,先來介紹Go
語言自帶的性能分析庫pprof
怎麼使用,後面兩篇會講解怎麼用pprof
對Echo
或者Gin
框架開發的應用進行性能分析以及如何使用pprof
對gRPC 服務進行性能分析。node
有興趣追更的同窗歡迎微信關注「網管叨bi叨」
Golang
是一個很是注重性能的語言,所以語言的內置庫裏就自帶了性能分析庫pprof
。git
性能分析和採集在計算機性能調試領域使用的術語是profile
,或者有時候也會使用profiling
表明性能分析這個行爲。因此pprof
名字裏的prof
來源於對單詞profile
的縮寫,profile
這個單詞的原意是畫像,那麼針對程序性能的畫像就是應用使用 CPU 和內存等等這些資源的狀況。都是哪些函數在使用這些資源?每一個函數使用的比例是多少?github
在Go
語言中,主要關注的程序運行狀況包括如下幾種:golang
若是你的應用是工具類應用,執行完任務就結束退出,可使用 runtime/pprof
[1]庫。web
好比要想進行 CPU Profiling,能夠調用 pprof.StartCPUProfile()
方法,它會對當前應用程序進行CPU使用狀況分析,並寫入到提供的參數中(w io.Writer
),要中止調用 StopCPUProfile()
便可。正則表達式
去除錯誤處理只須要三行內容,通常把部份內容寫在 main.go
文件中,應用程序啓動以後就開始執行:json
1. f, err := os.Create(*cpuprofile) 2. ... 3. pprof.StartCPUProfile(f) 4. defer pprof.StopCPUProfile()
應用執行結束後,就會生成一個文件,保存了咱們應用的 CPU使用狀況數據。api
想要得到內存的數據,直接使用 WriteHeapProfile
就行,不用 start
和 stop
這兩個步驟了:瀏覽器
1. f, err := os.Create(*memprofile) 2. pprof.WriteHeapProfile(f) 3. f.Close()
咱們的文章會把重點篇幅放在服務型應用的性能分析,因此關於工具型應用的性能分析就說這麼多。安全
若是你的應用是一直運行的,好比 web 應用或者gRPC
服務等,那麼可使用 net/http/pprof
庫,它可以在應用提供 HTTP 服務時進行分析。
若是使用了默認的 http.DefaultServeMux
(一般是代碼直接使用 http.ListenAndServe("0.0.0.0:8000", nil)
),只須要在代碼中添加一行,匿名引用net/http/pprof
:
import _ "net/http/pprof"
若是你使用自定義的 ServerMux
複用器,則須要手動註冊一些路由規則:
1. r.HandleFunc("/debug/pprof/", pprof.Index) 2. r.HandleFunc("/debug/pprof/heap", pprof.Index) 3. r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 4. r.HandleFunc("/debug/pprof/profile", pprof.Profile) 5. r.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 6. r.HandleFunc("/debug/pprof/trace", pprof.Trace)
註冊完後訪問http://localhost/debug/pprof
端點,它會獲得相似下面的頁面內容:
1. Types of profiles available: 2. Count Profile 3. // 下面是一些可訪問的/debug/pprof/目錄下的路由 4. 2 allocs 5. 0 block 6. 0 cmdline 7. 36 goroutine 8. 2 heap 9. 0 mutex 10. 0 profile 11. 13 threadcreate 12. 0 trace 13. full goroutine stack dump 14. Profile Descriptions: 16. // 下面是對上面那些路由頁面裏展現的性能分析數據的解釋 17. allocs: A sampling of all past memory allocations 18. block: Stack traces that led to blocking on synchronization primitives 19. cmdline: The command line invocation of the current program 20. goroutine: Stack traces of all current goroutines 21. heap: A sampling of memory allocations of live objects. You can specify the gc GET parameter to run GC before taking the heap sample. 22. mutex: Stack traces of holders of contended mutexes 23. profile: CPU profile. You can specify the duration in the seconds GET parameter. After you get the profile file, use the go tool pprof command to investigate the profile. 24. threadcreate: Stack traces that led to the creation of new OS threads 25. trace: A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace.
這個路徑下幾個須要重點關注的子頁面有:
/debug/pprof/profile
:訪問這個連接會自動進行 CPU profiling,持續 30s,並生成一個文件供下載,能夠經過帶參數?=seconds=60
進行60秒的數據採集/debug/pprof/heap
:Memory Profiling 的路徑,訪問這個連接會獲得一個內存 Profiling 結果的文件/debug/pprof/block
:block Profiling 的路徑/debug/pprof/goroutines
:運行的 goroutines 列表,以及調用關係直接訪問這些頁面產生的性能分析數據咱們是分析不過來什麼的,Go在1.11
版本後在它自帶的工具集go tool
裏內置了pprof
工具來分析由pprof
庫生成的數據文件。
經過上面的設置能夠獲取服務的性能數據後,接下來就可使用go tool pprof
工具對這些數據進行分析和保存了,通常都是使用pprof
經過HTTP
訪問上面列的那些路由端點直接獲取到數據後再進行分析,獲取到數據後pprof
會自動讓終端進入交互模式。在交互模式裏pprof
爲咱們提供了很多分析各類指標的子命令,在交互模式下鍵入help
後就會列出全部子命令。
NOTE pprof子命令的使用方法能夠參考
pprof --help
或者 pprof 文檔[2]。
進行CPU
性能分析直接用go tool pprof
訪問上面說的/debug/pprof/profile
端點便可,等數據採集完會自動進入命令行交互模式。
1. ➜ go tool pprof http://localhost/debug/pprof/profile 2. Fetching profile over HTTP from http://localhost/debug/pprof/profile 3. Saved profile in /Users/Kev/pprof/pprof.samples.cpu.005.pb.gz 4. Type: cpu 5. Time: Nov 15, 2020 at 3:32pm (CST) 6. Duration: 30.01s, Total samples = 0 7. No samples were found with the default sample value type. 8. Try "sample_index" command to analyze different sample values. 9. Entering interactive mode (type "help" for commands, "o" for options) 10. (pprof)
默認採集時長是 30s,若是在 url 最後加上 ?seconds=60
參數能夠調整採集數據的時間爲 60s。
採集完成咱們就進入了一個交互式命令行,能夠對解析的結果進行查看和導出。能夠經過 help
來查看支持的子命令有哪些。
NOTE: 若是pprof
用性能數據生成分析圖的話、包括後面的go-torch火焰圖都依賴軟件graphviz
Mac 用戶直接
brew install graphviz
就能安裝,其餘系統官網下載頁面也有提供安裝包,請訪問https://graphviz.org/download/
一個有用的命令是 topN
,它列出最耗時間的地方:
1. (pprof) top10 2. 130ms of 360ms total (36.11%) 3. Showing top 10 nodes out of 180 (cum >= 10ms) 4. flat flat% sum% cum cum% 5. 20ms 5.56% 5.56% 100ms 27.78% encoding/json.(*decodeState).object 6. 20ms 5.56% 11.11% 20ms 5.56% runtime.(*mspan).refillAllocCache 7. 20ms 5.56% 16.67% 20ms 5.56% runtime.futex 8. 10ms 2.78% 19.44% 10ms 2.78% encoding/json.(*decodeState).literalStore 9. 10ms 2.78% 22.22% 10ms 2.78% encoding/json.(*decodeState).scanWhile 10. 10ms 2.78% 25.00% 40ms 11.11% encoding/json.checkValid 11. 10ms 2.78% 27.78% 10ms 2.78% encoding/json.simpleLetterEqualFold 12. 10ms 2.78% 30.56% 10ms 2.78% encoding/json.stateBeginValue 13. 10ms 2.78% 33.33% 10ms 2.78% encoding/json.stateEndValue 14. 10ms 2.78% 36.11% 10ms 2.78% encoding/json.stateInString
每一行表示一個函數的信息。前兩列表示函數在 CPU 上運行的時間以及百分比;第三列是當前全部函數累加使用 CPU 的比例;第四列和第五列表明這個函數以及子函數運行所佔用的時間和比例(也被稱爲累加值 cumulative
),應該大於等於前兩列的值;最後一列就是函數的名字。若是應用程序有性能問題,上面這些信息應該能告訴咱們時間都花費在哪些函數的執行上。
pprof
不只能打印出最耗時的地方(top
),還能列出函數代碼以及對應的取樣數據(list
)、彙編代碼以及對應的取樣數據(disasm
),並且能以各類樣式進行輸出,好比 svg
、gif
、png
等等。
其中一個很是便利的是 web
命令,在交互模式下輸入 web
,就能自動生成一個 svg
文件,並跳轉到瀏覽器打開,生成了一個函數調用圖(這個功能須要安裝graphviz
後才能使用)。
圖中每一個方框對應應用程序運行的一個函數,方框越大表明函數執行的時間越久(函數執行時間會包含它調用的子函數的執行時間,但並非正比的關係);方框之間的箭頭表明着調用關係,箭頭上的數字表明被調用函數的執行時間。
這裏還要提兩個比較有用的方法,若是應用比較複雜,生成的調用圖特別大,看起來很亂,有兩個辦法能夠優化:
web funcName
的方式,只打印和某個函數相關的內容go tool pprof
命令時加上 --nodefration
參數,能夠忽略內存使用較少的函數,好比--nodefration=0.05
表示若是調用的子函數使用的 CPU、memory 不超過 5%,就忽略它,不要顯示在圖片中。想更細緻分析,就要精確到代碼級別了,看看每行代碼的耗時,直接定位到出現性能問題的那行代碼。pprof
也能作到,list
命令後面跟着一個正則表達式,就能查看匹配函數的代碼以及每行代碼的耗時:
1. (pprof) list podFitsOnNode 2. Total: 120ms 3. 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 4. 0 20ms (flat, cum) 16.67% of Total 5. . . 230: 6. . . 231:// Checks whether node with a given name and NodeInfo satisfies all predicateFuncs. 7. . . 232:func podFitsOnNode(pod *api.Pod, meta interface{}, info *schedulercache.NodeInfo, predicateFuncs map[string]algorithm.FitPredicate) (bool, []algorithm.PredicateFailureReason, error) { 8. . . 233: var failedPredicates []algorithm.PredicateFailureReason 9. . . 234: for _, predicate := range predicateFuncs { 10. . 20ms 235: fit, reasons, err := predicate(pod, meta, info) 11. . . 236: if err != nil { 12. . . 237: err := fmt.Errorf("SchedulerPredicates failed due to %v, which is unexpected.", err) 13. . . 238: return false, []algorithm.PredicateFailureReason{}, err 14. . . 239: } 15. . . 240: if !fit {
要想得到內存使用 Profiling 信息,只須要把數據源修改一下就行(對於 HTTP 方式來講就是修改 url 的地址,從 /debug/pprof/profile
改爲 /debug/pprof/heap
):
1. ➜ go tool pprof http://localhost/debug/pprof/heap 2. Fetching profile from http://localhost/debug/pprof/heap 3. Saved profile in 4. ...... 5. (pprof)
和 CPU Profiling 使用同樣,使用 top N
能夠打印出使用內存最多的函數列表:
1. (pprof) top 2. 11712.11kB of 14785.10kB total (79.22%) 3. Dropped 580 nodes (cum <= 73.92kB) 4. Showing top 10 nodes out of 146 (cum >= 512.31kB) 5. flat flat% sum% cum cum% 6. 2072.09kB 14.01% 14.01% 2072.09kB 14.01% k8s.io/kubernetes/vendor/github.com/beorn7/perks/quantile.NewTargeted 7. 2049.25kB 13.86% 27.87% 2049.25kB 13.86% k8s.io/kubernetes/pkg/api/v1.(*ResourceRequirements).Unmarshal 8. 1572.28kB 10.63% 38.51% 1572.28kB 10.63% k8s.io/kubernetes/vendor/github.com/beorn7/perks/quantile.(*stream).merge 9. 1571.34kB 10.63% 49.14% 1571.34kB 10.63% regexp.(*bitState).reset 10. 1184.27kB 8.01% 57.15% 1184.27kB 8.01% bytes.makeSlice 11. 1024.16kB 6.93% 64.07% 1024.16kB 6.93% k8s.io/kubernetes/pkg/api/v1.(*ObjectMeta).Unmarshal 12. 613.99kB 4.15% 68.23% 2150.63kB 14.55% k8s.io/kubernetes/pkg/api/v1.(*PersistentVolumeClaimList).Unmarshal 13. 591.75kB 4.00% 72.23% 1103.79kB 7.47% reflect.Value.call 14. 520.67kB 3.52% 75.75% 520.67kB 3.52% k8s.io/kubernetes/vendor/github.com/gogo/protobuf/proto.RegisterType 15. 512.31kB 3.47% 79.22% 512.31kB 3.47% k8s.io/kubernetes/pkg/api/v1.(*PersistentVolumeClaimStatus).Unmarshal
每一列的含義也是相似的,只不過從 CPU
使用時長變成了內存使用大小,就很少解釋了。
相似的,web
命令也能生成 svg
圖片在瀏覽器中打開,從中能夠看到函數調用關係,以及每一個函數的內存使用多少。
須要注意的是,默認狀況下,統計的是內存使用大小,若是執行命令的時候加上 --inuse_objects
能夠查看每一個函數分配的對象數;--alloc-space
查看分配的內存空間大小。
火焰圖(Flame Graph)是 Bredan Gregg 建立的一種性能分析圖表,由於它的樣子近似火焰而得名。上面的 profiling
結果也轉換成火焰圖,這裏咱們要介紹一個工具:go-torch[3]。這是 uber 開源的一個工具,能夠直接讀取 pprof
的 profiling
數據,並生成一個火焰圖的 svg 文件。
img
火焰圖 svg 文件能夠經過瀏覽器打開,它對於調用圖的優勢是:能夠經過點擊每一個方塊來分析它上面的內容。
火焰圖的調用順序從下到上,每一個方塊表明一個函數,它上面一層表示這個函數會調用哪些函數,方塊的大小表明了佔用 CPU 使用的長短。火焰圖的配色並無特殊的意義,默認的紅、黃配色是爲了更像火焰而已。
go-torch 工具的使用很是簡單,沒有任何參數的話,它會嘗試從 http://localhost/debug/pprof/profile
獲取 profiling 數據。它有三個經常使用的參數能夠調整:
-u --url
:要訪問的 URL,這裏只是主機和端口部分-s --suffix
:pprof profile 的路徑,默認爲 /debug/pprof/profile
--seconds
:要執行 profiling 的時間長度,默認爲 30s要生成火焰圖,須要事先安裝 FlameGraph[4]工具,這個工具的安裝很簡單,只要把對應的可執行文件放到 $PATH
目錄下就行。
今天的文章把Go語言的性能分析庫pprof
的安裝和使用方法大致流程走了一遍,重點講解了一下最經常使用的幾個性能分析命令以及如何用pprof
採集的profile
數據找出程序裏最耗費性能的部分。相信有了pprof
的幫助在遇到須要優化程序性能的時候咱們能有更多的參照指標從而有的放矢地對程序性能進行優化改進。
在使用pprof
採集數據的時候必定要注意下面兩點:
pprof
拿到的數據,若是你是在本地運行程序的話最好用Postman
或者Jmeter
這些工具作個簡單的併發訪問。pprof
使用的那些路由,不要在生產環境上應用。這篇文章就說這麼多,後面的文章會介紹怎麼在Echo
和Gin
框架下使用pprof
,以及如何用pprof
分析gRPC
服務的性能。求關注、求點贊、求轉發!我是網管,會在這裏每週堅持輸出原創,咱們下期再見吧。
[1]
runtime/pprof
: https://golang.org/pkg/runtim...
[2]
pprof 文檔: https://github.com/google/ppr...
[3]
go-torch: https://github.com/uber/go-torch
[4]
FlameGraph: https://github.com/brendangre...
文章推薦