通過例子學(xué) Go pprof
對于大多數(shù) Gopher 而言,一般平時最主要的工作內(nèi)容除了實現(xiàn)各種無聊的業(yè)務(wù)邏輯之外,剩下的就是解決各種瑣碎的問題。比如:查詢性能瓶頸在哪里?查詢內(nèi)存泄漏在哪里?好在 pprof 是處理此類問題的利器,共有兩套標(biāo)準(zhǔn)庫,分別適用于不同的場景:
runtime/pprof[1]:采集工具型應(yīng)用運行數(shù)據(jù)進(jìn)行分析 net/http/pprof[2]:采集服務(wù)型應(yīng)用運行時數(shù)據(jù)進(jìn)行分析
命令行工具「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)部分:

為了檢測 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)部分:

實際應(yīng)用中,最好對你的瓶頸是 onCPU 還是 offCPU 有一個大體的認(rèn)識,進(jì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,用命令行):

通過 top 命令可以很直觀的看出哪里可能出現(xiàn)了內(nèi)存泄漏問題。不過這里有一個需要說明的問題是內(nèi)存占用大的地方本身可能是正常的,與內(nèi)存的絕對值大小相比,我們更應(yīng)該關(guān)注的是不同時間點內(nèi)存相對變化大小,這里可以使用參數(shù) base 或者 diff_base:

本文篇幅有限,無法列舉更多的例子,有興趣的讀者推薦參考「golang pprof 實戰(zhàn)[3]」。
參考資料
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/
