1. 簡介
Golang 是一個對性能特別看重的語言,因此語言中自帶了 一些性能分析工具。可以生成相應的Profile(概要文件),譬如CPU Profile、Memory Profile、Block Profile、Mutex Profile、Goroutine Profile等等
CPU Profile:報告程序的 CPU 使用情況,按照一定頻率採集應用程序在 CPU 和寄存器上面的數據
Memory Profile(Heap Profile):報告程序的內存使用情況
Block Profile:報告 goroutines 不在運行狀態的情況,可以用來分析和查找死鎖等性能瓶頸問題
Goroutine Profile:報告 goroutines 的使用情況,有哪些 goroutine,它們的調用關係是怎樣的
2. 使用方法
pprof命令:
usage:
Produce output in the specified format.
pprof <format> [options] [binary] <source> ...
Omit the format to get an interactive shell whose commands can be used
to generate various views of a profile
pprof [options] [binary] <source> ...
Omit the format and provide the "-http" flag to get an interactive web
interface at the specified host:port that can be used to navigate through
various views of a profile.
pprof -http [host]:[port] [options] [binary] <source> ...
Details:
Output formats (select at most one):
-callgrind Outputs a graph in callgrind format
-comments Output all profile comments
-disasm Output assembly listings annotated with samples
-dot Outputs a graph in DOT format
-eog Visualize graph through eog
-evince Visualize graph through evince
-gif Outputs a graph image in GIF format
-gv Visualize graph through gv
-kcachegrind Visualize report in KCachegrind
-list Output annotated source for functions matching regexp
-pdf Outputs a graph in PDF format
-peek Output callers/callees of functions matching regexp
-png Outputs a graph image in PNG format
-proto Outputs the profile in compressed protobuf format
-ps Outputs a graph in PS format
-raw Outputs a text representation of the raw profile
-svg Outputs a graph in SVG format
-tags Outputs all tags in the profile
-text Outputs top entries in text form
-top Outputs top entries in text form
-topproto Outputs top entries in compressed protobuf format
-traces Outputs all profile samples in text form
-tree Outputs a text rendering of call graph
-web Visualize graph through web browser
-weblist Display annotated source in a web browser
Options:
-call_tree Create a context-sensitive call tree
-compact_labels Show minimal headers
-divide_by Ratio to divide all samples before visualization
-drop_negative Ignore negative differences
-edgefraction Hide edges below <f>*total
-focus Restricts to samples going through a node matching regexp
-hide Skips nodes matching regexp
-ignore Skips paths going through any nodes matching regexp
-mean Average sample value over first value (count)
-nodecount Max number of nodes to show
-nodefraction Hide nodes below <f>*total
-normalize Scales profile based on the base profile.
-output Output filename for file-based outputs
-positive_percentages Ignore negative samples when computing percentages
-prune_from Drops any functions below the matched frame.
-relative_percentages Show percentages relative to focused subgraph
-sample_index Sample value to report (0-based index or name)
-show Only show nodes matching regexp
-source_path Search path for source files
-tagfocus Restricts to samples with tags in range or matched by regex
p
-taghide Skip tags matching this regexp
-tagignore Discard samples with tags in range or matched by regexp
-tagshow Only consider tags matching this regexp
-trim Honor nodefraction/edgefraction/nodecount defaults
-unit Measurement units to display
Option groups (only set one per group):
cumulative
-cum Sort entries based on cumulative weight
-flat Sort entries based on own weight
granularity
-addresses Aggregate at the function level.
-addressnoinlines Aggregate at the function level, including functions' ad
dresses in the output.
-files Aggregate at the file level.
-functions Aggregate at the function level.
-lines Aggregate at the source code line level.
-noinlines Aggregate at the function level.
binary 是應用的二進制文件,用來解析各種符號;source 表示 profile 數據的來源,可以是本地的文件,也可以是 http 地址
在pprof界面,通過 TopN 命令顯示重要信息。
如果我們在top命令後加入標籤–cum,那麼輸出的列表就是以累積取樣計數爲順序的
當開啓CPU Profile時,Go程序每秒鐘進行100次採樣。
The web command writes a graph of the profile data in SVG format and opens it in a web browser. (There is also a gv command that writes PostScript and opens it in Ghostview. For either command, you need graphviz installed.)
通過 Web 命令以SVG格式創建 Profile數據表,並且在web瀏覽器中進行展示。
同時,也可以通過gv命令寫PostScript,並且在Ghostview中打開。不管哪一個命令,都需要安裝graphviz。
在圖中看到耗時的操作時,譬如 function1,那麼可以通過如下命令仔細觀察:
web function1
可以通過如下命令查看源碼
list function1
go tool pprof 最簡單的使用方式爲 go tool pprof [binary] [source],binary 是應用的二進制文件,用來解析各種符號;source 表示 Profile 數據的來源,可以是本地的文件,也可以是 http 地址。
如果程序名爲testDemo,profile文件爲testDemo.prof,那麼通過如下命令就能展示程序的一些運行信息:
go tool pprof testDemo testDemo.prof
那麼問題來了,如何生成Profile文件?
3. Profile文件產生方法
根據程序的性質不同,生成profile文件的方式也不同。
3.1 Web應用
對於Web應用,非常簡單,如果使用了默認的 http.DefaultServeMux(通常是代碼直接使用 http.ListenAndServe(“0.0.0.0:8000”, nil)),在命令源碼文件中導入net/http/pprof包就可以,具體形式如下:
import _ "net/http/pprof"
如果你使用自定義的 Mux,則需要手動註冊一些路由規則:
r.HandleFunc("/debug/pprof/", pprof.Index)
r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
r.HandleFunc("/debug/pprof/profile", pprof.Profile)
r.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
r.HandleFunc("/debug/pprof/trace", pprof.Trace)
3.2 普通應用
方法一
對於普通的應用程序,可以採用和Web應用相同的方法,在main函數中添加如下代碼:
import "net/http"
import "log"
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
或者
package main
import (
"net/http"
_ "net/http/pprof"
)
func hiHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hi"))
}
func main() {
http.HandleFunc("/", hiHandler)
http.ListenAndServe(":8080", nil)
}
**方法二 **
主動將各種概要文件寫入文件。
CPU概要文件
對於CPU Profile,可以採用如下方式,在main函數的入口處調用 WriteCPUProfile 函數:
var cpuProfile = flag.String("cpuProfile", "", "write cpu profile to file")
func WriteCPUProfile() {
if *cpuProfile != "" {
f, err := os.Create(*cpuProfile)
if err != nil {
fmt.Fprintf(os.Stderr, "Can not create cpu profile output file: %s",
err)
return
}
if err := pprof.StartCPUProfile(f); err != nil {
fmt.Fprintf(os.Stderr, "Can not start cpu profile: %s", err)
f.Close()
return
}
defer pprof.StopCPUProfile() // 把記錄的概要信息寫到已指定的文件
}
}
或者
func startCPUProfile() {
if *cpuProfile != "" {
f, err := os.Create(*cpuProfile)
if err != nil {
fmt.Fprintf(os.Stderr, "Can not create cpu profile output file: %s",
err)
return
}
if err := pprof.StartCPUProfile(f); err != nil {
fmt.Fprintf(os.Stderr, "Can not start cpu profile: %s", err)
f.Close()
return
}
}
}
停止函數
func stopCPUProfile() {
if *cpuProfile != "" {
pprof.StopCPUProfile() // 把記錄的概要信息寫到已指定的文件
}
}
調用方法
var cpuProfile = flag.String("cpuprofile", "", "write a cpu profile to the named file during execution")
func main(){
startCPUProfile()
defer stopCPUProfile()
}
-cpuprofile 指定CPU概要文件的保存路徑。該路徑可以是相對路徑也可以是絕對路徑,但其父路徑必須已存在。
使用方法:
go run testDemo.go -p="runtime" -cpuprofile="./pprof/cpu.pprof"
-p是對命令源碼文件 testDemo.go 有效的。其含義是指定要解析依賴關係的代碼包的導入路徑。(明確)
內存概要文件
對於內存概要函數,分爲start和stop兩個函數:
var memProfile = flag.String("memprofile", "", "write a memory profile to the named file after execution")
var memProfileRate = flag.Int("memprofilerate", 0, "if > 0, sets runtime.MemProfileRate")
func startMemProfile() {
if *memProfile != "" && *memProfileRate > 0 {
runtime.MemProfileRate = *memProfileRate
}
}
memProfile是內存概要文件的路徑。memProfileRate的含義是分析器的取樣間隔,單位是字節。如果我們不給runtime.MemProfileRate變量賦值,內存使用情況的取樣操作也會照樣進行。此取樣操作會從用戶程序開始時啓動,且一直持續進行到用戶程序結束。runtime.MemProfileRate變量的默認值是512K字節。只有當我們顯式的將0賦給runtime.MemProfileRate變量之後,纔會取消取樣操作。
在默認情況下,內存使用情況的取樣數據只會被保存在運行時內存中,而保存到文件的操作只能由我們自己來完成。
停止函數:
func stopMemProfile() {
if *memProfile != "" {
f, err := os.Create(*memProfile)
if err != nil {
fmt.Fprintf(os.Stderr, "Can not create mem profile output file: %s", err)
return
}
if err = pprof.WriteHeapProfile(f); err != nil {
fmt.Fprintf(os.Stderr, "Can not write %s: %s", *memProfile, err)
}
f.Close()
}
}
stopMemProfile函數的功能是停止對內存使用情況的取樣操作。但是,它只做了將取樣數據保存到內存概要文件的操作。在stopMemProfile函數中,我們調用了函數pprof.WriteHeapProfile,並把代表內存概要文件的文件實例作爲了參數。如果pprof.WriteHeapProfile函數沒有返回錯誤,就說明數據已被寫入到了內存概要文件中。
-memprofile 指定內存概要文件的保存路徑。該路徑可以是相對路徑也可以是絕對路徑,但其父路徑必須已存在。
-memprofilerate 定義其值爲n。此標記指定每分配n個字節的堆內存時,進行一次取樣操作。
使用方法:
go run testDemo.go -p="runtime" -memprofile="./pprof/mem.pprof" -memprofilerate=10
程序阻塞概要文件
程序阻塞概要文件用於保存用戶程序中的Goroutine阻塞事件的記錄。
var blockProfile = flag.String("blockprofile", "", "write a goroutine blocking profile to the named file after execution")
var blockProfileRate = flag.Int("blockprofilerate", 1, "if > 0, calls runtime.SetBlockProfileRate()")
func startBlockProfile() {
if *blockProfile != "" && *blockProfileRate > 0 {
runtime.SetBlockProfileRate(*blockProfileRate)
}
}
blockProfile的含義爲程序阻塞概要文件的路徑。blockProfileRate的含義是分析器的取樣間隔,單位是次。默認取樣間隔爲1,即默認情況下,每發生一次Goroutine阻塞事件,分析器就會取樣一次。與內存使用情況記錄一樣,運行時系統對Goroutine阻塞事件的取樣操作也會貫穿於用戶程序的整個運行期。但是,如果我們通過runtime.SetBlockProfileRate函數將這個取樣間隔設置爲0或者負數,那麼這個取樣操作就會被取消。
將保存在運行時內存中的Goroutine阻塞事件記錄存放到指定的文件,代碼如下:
func stopBlockProfile() {
if *blockProfile != "" && *blockProfileRate >= 0 {
f, err := os.Create(*blockProfile)
if err != nil {
fmt.Fprintf(os.Stderr, "Can not create block profile output file: %s", err)
return
}
if err = pprof.Lookup("block").WriteTo(f, 0); err != nil {
fmt.Fprintf(os.Stderr, "Can not write %s: %s", *blockProfile, err)
}
f.Close()
}
}
在創建程序阻塞概要文件之後,stopBlockProfile函數會先通過函數pprof.Lookup將保存在運行時內存中的內存使用情況記錄取出,並在記錄的實例上調用WriteTo方法將記錄寫入到文件中。
-blockprofile 指定程序阻塞概要文件的保存路徑。該路徑可以是相對路徑也可以是絕對路徑,但其父路徑必須已存在。
-blockprofilerate 定義其值爲n。此標記指定每發生n次Goroutine阻塞事件時,進行一次取樣操作。
使用方法:
go run testDemo.go -p="runtime" -blockprofile="./pprof/block.pprof" -blockprofilerate=10
通過pprof.Lookup函數取出更多種類的取樣記錄,具體如下:
名稱 | 說明 | 取樣頻率 |
---|---|---|
goroutine | 活躍Goroutine的信息的記錄。 | 僅在獲取時取樣一次。 |
threadcreate | 系統線程創建情況的記錄。 | 僅在獲取時取樣一次。 |
heap | 堆內存分配情況的記錄。 | 默認每分配512K字節時取樣一次。 |
block | Goroutine阻塞事件的記錄。 | 默認每發生一次阻塞事件時取樣一次 |
5. 效果展示
5.1 Web應用
爲方便展示效果,先以Web應用展示Pprof的使用,示例程序見Github:
查看堆信息:
go tool pprof http://localhost:9090/debug/pprof/heap
在pprof界面中,輸入web命令:
Web_heap
查看阻塞信息:
go tool pprof http://localhost:9090/debug/pprof/block
查看鎖信息(to look at the holders of contended mutexes, after calling runtime.SetMutexProfileFraction in your program)
go tool pprof http://localhost:9090/debug/pprof/mutex
查看60s的CPU信息
go tool pprof http://localhost:9090/debug/pprof/profile?seconds=60
輸入web指令,輸出信息:
web_cpu
採集5s內的程序執行路徑信息:
wget http://localhost:9090/debug/pprof/trace?seconds=5
如果需要查看所有可用的profile,在瀏覽器中打開:
http://localhost:9090/debug/pprof/
結果:
heap信息如下:
5.2 普通應用
測試程序如下見Github,只進行cpu性能分析.
通過 go install havlak1.go生成可執行程序havlak1
首先產生prof文件:
havlak1 -cpuprofile=havlak1.prof
其次,對prof文件進行分析:
go tool pprof havlak1 havlak1.prof
在pprof操作界面:
F:\ProgramTest\Golang\goBin>go tool pprof havlak1.exe havlak1.prof
File: havlak1.exe
Type: cpu
Time: Jul 25, 2019 at 6:34am (CST)
Duration: 20.10s, Total samples = 25.87s (128.68%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)
輸入以下指令查看最佔CPU的10個函數:
top 10
結果如下:
Showing nodes accounting for 19400ms, 74.99% of 25870ms total
Dropped 107 nodes (cum <= 129.35ms)
Showing top 10 nodes out of 55
flat flat% sum% cum cum%
3540ms 13.68% 13.68% 3620ms 13.99% runtime.mapaccess1_fast64
3100ms 11.98% 25.67% 6690ms 25.86% runtime.scanobject
2420ms 9.35% 35.02% 12460ms 48.16% main.FindLoops
2070ms 8.00% 43.02% 3410ms 13.18% runtime.mallocgc
2020ms 7.81% 50.83% 2020ms 7.81% runtime.heapBitsForObject
1840ms 7.11% 57.94% 1860ms 7.19% runtime.greyobject
1410ms 5.45% 63.39% 2800ms 10.82% runtime.mapassign_fast64ptr
1330ms 5.14% 68.53% 3530ms 13.65% main.DFS
890ms 3.44% 71.98% 890ms 3.44% runtime.heapBitsSetType
780ms 3.02% 74.99% 780ms 3.02% runtime.memclrNoHeapPointers
top 5 -cum
結果如下:
Showing nodes accounting for 2.50s, 9.66% of 25.87s total
Dropped 107 nodes (cum <= 0.13s)
Showing top 5 nodes out of 55
flat flat% sum% cum cum%
0 0% 0% 12.53s 48.43% main.main
0 0% 0% 12.53s 48.43% runtime.main
0 0% 0% 12.46s 48.16% main.FindHavlakLoops
2.42s 9.35% 9.35% 12.46s 48.16% main.FindLoops
0.08s 0.31% 9.66% 9.06s 35.02% runtime.systemstack
每一行表示一個函數的信息。前兩列表示函數在 CPU 上運行的時間以及百分比;第三列是當前所有函數累加使用 CPU 的比例;第四列和第五列代表這個函數以及子函數運行所佔用的時間和比例(也被稱爲累加值 cumulative),應該大於等於前兩列的值;最後一列就是函數的名字。如果應用程序有性能問題,上面這些信息應該能告訴我們時間都花費在哪些函數的執行上了。
pprof 不僅能打印出最耗時的地方(top),還能列出函數代碼以及對應的取樣數據(list)、彙編代碼以及對應的取樣數據(disasm),而且能以各種樣式進行輸出,比如 svg、gv、callgrind、png、gif等等。
其中一個非常便利的是 web 命令,在交互模式下輸入 web,就能自動生成一個 svg 文件,並跳轉到瀏覽器打開,生成了一個函數調用圖(havlak1_web):
web
Web生成的圖包含了更多的信息,而且可視化的圖像能讓我們更清楚地理解整個應用程序的全貌。圖中每個方框對應一個函數,方框越大代表執行的時間越久(包括它調用的子函數執行時間,但並不是正比的關係);方框之間的箭頭代表着調用關係,箭頭上的數字代表被調用函數的執行時間。
web mapaccess1
6. go test
go test 命令有兩個參數和 pprof 相關,它們分別指定生成的 CPU 和 Memory profiling 保存的文件:
-cpuprofile:cpu profiling 數據要保存的文件地址
-memprofile:memory profiling 數據要報文的文件地址
go test -bench=. -cpuprofile=cpu.prof
go test -bench=. -memprofile=mem.prof
1. go tool pprof cpu.prof
2. go tool pprof -http=:8080 cpu.prof
7. 火焰圖(待續)
8. 參考文獻
Profiling and optimizing Go web applications