淺談Pprof,你了解多少?
本文轉(zhuǎn)載自微信公眾號「架構(gòu)技術(shù)漫談」,作者LA0WAN9。轉(zhuǎn)載本文請聯(lián)系架構(gòu)技術(shù)漫談公眾號。
對于大多數(shù) Gopher 而言,一般平時最主要的工作內(nèi)容除了實現(xiàn)各種無聊的業(yè)務(wù)邏輯之外,剩下的就是解決各種瑣碎的問題。比如:查詢性能瓶頸在哪里?查詢內(nèi)存泄漏在哪里?好在 pprof 是處理此類問題的利器,共有兩套標(biāo)準(zhǔn)庫,分別適用于不同的場景:
- runtime/pprof[1]:采集工具型應(yīng)用運行數(shù)據(jù)進行分析
- net/http/pprof[2]:采集服務(wù)型應(yīng)用運行時數(shù)據(jù)進行分析
命令行工具「go test」就包含了 runtime/pprof,相關(guān)參數(shù)請參考「go help testflag」:
- shell> go test -cpuprofile cpu.out -memprofile mem.out -bench .
不過和 runtime/pprof 相比,更常用的是 net/http/pprof,接下來我們主要通過它來解決一些常見問題,想要激活 net/http/pprof 的話很簡單,只要導(dǎo)入對應(yīng)的包并啟動服務(wù)即可:
- import _ "net/http/pprof"
- func main() {
- _ = http.ListenAndServe("localhost:6060", nil)
- }
需要注意的是,千萬別讓外網(wǎng)訪問到 pprof,否則可能會導(dǎo)致出現(xiàn)安全問題。有興趣的讀者可以嘗試通過 google 搜索「intitle:/debug/pprof/ inurl:/debug/pprof/」看看反面例子。
Profile
pprof 預(yù)置了很多種不同類型的 profile,我們可以按照自己的需要選擇:
- allocs:A sampling of all past memory allocations
- block:Stack traces that led to blocking on synchronization primitives
- goroutine:Stack traces of all current goroutines
- heap:A sampling of memory allocations of live objects
- mutex:Stack traces of holders of contended mutexes
- profile:CPU profile
- threadcreate:Stack traces that led to the creation of new OS threads
其中最常用的是 profile 和 heap,分別用來診斷 CPU 和內(nèi)存問題。
CPU profiling
演示代碼模擬了 CPU 密集型任務(wù)(onCPU)和耗時的網(wǎng)絡(luò)請求(offCPU):
- package main
- import (
- "log"
- "net/http"
- _ "net/http/pprof"
- "runtime"
- "time"
- "github.com/felixge/fgprof"
- )
- const cpuTime = 1000 * time.Millisecond
- func main() {
- runtime.SetBlockProfileRate(1)
- runtime.SetMutexProfileFraction(1)
- go func() {
- http.Handle("/debug/fgprof", fgprof.Handler())
- log.Println(http.ListenAndServe(":6060", nil))
- }()
- for {
- cpuIntensiveTask()
- slowNetworkRequest()
- }
- }
- func cpuIntensiveTask() {
- start := time.Now()
- for time.Since(start) <= cpuTime {
- for i := 0; i < 1000; i++ {
- _ = i
- }
- }
- }
- func slowNetworkRequest() {
- resp, err := http.Get("http://httpbin.org/delay/1")
- if err != nil {
- log.Fatal(err)
- }
- defer resp.Body.Close()
- }
通過 go tool pprof 查看 /debug/pprof/profile:
- go tool pprof -http :8080 http://localhost:6060/debug/pprof/profile
結(jié)果發(fā)現(xiàn) profile 只能檢測到 onCPU(也就是 cpuIntensiveTask)部分,卻不能檢測到 offCPU (也就是 slowNetworkRequest)部分:
profile
為了檢測 offCPU 部分,我們引入 fgprof,通過 go tool pprof 查看 /debug/fgprof:
- go tool pprof -http :8080 http://localhost:6060/debug/fgprof
結(jié)果發(fā)現(xiàn) fgprof 不僅能檢測到 onCPU(也就是 cpuIntensiveTask)部分,還能檢測到 offCPU (也就是 slowNetworkRequest)部分:
fgprof
實際應(yīng)用中,最好對你的瓶頸是 onCPU 還是 offCPU 有一個大體的認(rèn)識,進而選擇合適的工具,如果不確定就直接用 fgprof,不過需要注意的是 fgprof 對性能的影響較大。
Memory profiling
演示代碼模擬了一段有內(nèi)存泄漏問題的程序:
- package main
- import (
- "log"
- "net/http"
- _ "net/http/pprof"
- "time"
- )
- func main() {
- go func() {
- log.Println(http.ListenAndServe(":6060", nil))
- }()
- for {
- leak()
- }
- }
- func leak() {
- s := make([]string, 10)
- for i := 0; i < 10000000; i++ {
- s = append(s, "leak")
- if (i % 10000) == 0 {
- time.Sleep(1 * time.Second)
- }
- _ = s
- }
- }
通過 go tool pprof 查看 /debug/pprof/head(這次不用 web,用命令行):
heap
通過 top 命令可以很直觀的看出哪里可能出現(xiàn)了內(nèi)存泄漏問題。不過這里有一個需要說明的問題是內(nèi)存占用大的地方本身可能是正常的,與內(nèi)存的絕對值大小相比,我們更應(yīng)該關(guān)注的是不同時間點內(nèi)存相對變化大小,這里可以使用參數(shù) base 或者 diff_base:
heap
本文篇幅有限,無法列舉更多的例子,有興趣的讀者推薦參考「golang pprof 實戰(zhàn)[3]」。
參考資料
[1]runtime/pprof: https://golang.org/pkg/runtime/pprof/
[2]net/http/pprof: https://golang.org/pkg/net/http/pprof/
[3]golang pprof 實戰(zhàn): https://blog.wolfogre.com/posts/go-ppof-practice/