Go 常用包: 調試利器 pprof 使用 -上-
- 介紹
Go
語言中的pprof
指對於指標或特徵的分析(Profiling
),通過分析不僅可以查找到程序中的錯誤(內存泄漏、race 衝突、協程泄漏),也能對程序進行優化(例如 CPU 利用率不足)。
由於Go
語言運行時的指標不對外暴露,因此有標準庫net/http/pprof
和runtime/pprof
用於與外界交互。其中net/http/pprof
提供了一種通過http
訪問的便利方式,用於用戶調試和獲取樣本特徵數據。
對特徵文件進行分析要依賴谷歌推出的分析工具
pprof
,該工具在 Go 安裝時即存在。
- 收集樣本
在通過pprof
進行特徵分析時,需要執行兩個步驟:收集樣本和分析樣本
pprof
採樣數據主要有三種獲取方式:
-
net/http/pprof
: 通過http
服務獲取Profile
採樣文件,簡單易用,適用於對應用程序的整體監控。底層也是通過runtime/pprof
實現。 -
runtime/pprof
: 手動調用runtime.StartCPUProfile
或者runtime.StopCPUProfile
等 API 來生成和寫入採樣文件,靈活性更高。 -
go test
: 通過go test -cpuprofile cpu.pprof -memprofile mem.pprof
生成採樣文件, 適用對函數進行鍼對性測試。其中-cpuprofile
:生成 CPU 性能測試信息;-memprofile
:生成內存佔用信息;
2.1 使用:net/http/pprof
1. 源碼詳情
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
_ "net/http/pprof" // 導入pprof
"strings"
"time"
)
func init() {
//開啓http端口,用協程的方式監聽,否則會阻塞
go func() {
if err := http.ListenAndServe(":6060", nil); err != nil {
fmt.Println("pprof err:",err)
}
}()
}
func main() {
engine := gin.Default()
engine.GET("/test", func(context *gin.Context) {
context.JSON(200,gin.H{
"msg":"success",
})
})
testPprofHeap()
_ = engine.Run(":8080")
}
// 模擬內存使用增加
func testPprofHeap() {
go func() {
var stringSlice []string
for {
time.Sleep(time.Second *2)
repeat := strings.Repeat("hello,world", 50000)
stringSlice = append(stringSlice,repeat)
fmt.Printf("time:%d length:%d \n",time.Now().Unix(),len(stringSlice))
}
}()
}
2. 訪問端口
2.2 使用:runtime/pprof
通過http
收集樣本是在實踐中最常見的方式,但有時可能不太適合,例如對於一個測試程序或只跑一次的定時任務。可以調用runtime/pprof的StartCPUProfile
函數,這樣,在程序調用StopCPUProfile
函數停止之後,即可指定特徵文件保存的位置。
1. 代碼詳情
package tests
import (
"os"
"runtime/pprof"
"testing"
"time"
)
func TestRuntimePProf(t *testing.T) {
// 打開文件
f, err := os.Create("./out.pprof")
if err != nil {
t.Errorf("文件打開失敗:%v", err)
return
}
// 調用
err = pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
if err != nil {
t.Errorf("StartCPUProfile:%v", err)
}
// 測試單獨函數
testPprof()
}
// 模擬內存使用增加
func testPprof() {
ch := make(chan bool)
go func() {
for i := 0; i < 20; i++ {
time.Sleep(time.Millisecond * 200)
}
ch <- true
}()
<-ch
}
2. 運行測試
# 運行
➜ go test pprof_test.go -v
=== RUN TestRuntimePProf
--- PASS: TestRuntimePProf (5.05s)
PASS
ok command-line-arguments 5.448s
# 查看pprof
➜ go tool pprof out.pprof
Type: cpu
Time: Nov 15, 2021 at 4:09pm (CST)
Duration: 4.06s, Total samples = 0
No samples were found with the default sample value type.
Try "sample_index" command to analyze different sample values.
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)
2.3 使用go test
使用格式 go test . -x 文件
-
-cpuprofile
: 生成CPU
性能信息。 -
-memprofile
: 生成內存佔用信息。 -
-mutexprofile
:生成鎖爭用情況。
1. 代碼詳情
package tests
import (
"strings"
"testing"
"time"
)
func TestWithPProf(t *testing.T) {
ch := make(chan bool)
go func() {
var stringSlice []string
for i := 0; i < 20; i++ {
repeat := strings.Repeat("hello,world", 50000)
stringSlice = append(stringSlice,repeat)
time.Sleep(time.Millisecond * 500)
}
ch <- true
}()
<-ch
}
2. 運行測試
# 運行單元測試
➜ go test . -cpuprofile test.pprof -memprofile mem.pprof
# 查看內存pprof
➜ go tool pprof mem.pprof
Type: alloc_space
Time: Nov 15, 2021 at 4:22pm (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)
- 分析樣本
pprof
提供了很多維度的分析,若想分析某一個維度信息,可直接使用go tool pprof http://ip:port/debug/pprof/維度
,進入交互式分析。
3.1 分析維度
-
allocs
:查看過去所有內存分配的樣本,訪問路徑爲/debug/pprof/allocs
。 -
block
:查看導致阻塞同步的堆棧跟蹤,訪問路徑爲/debug/pprof/block
。 -
cmdline
:當前程序的命令行的完整調用路徑。 -
goroutine
:查看當前所有運行的 goroutines 堆棧跟蹤,訪問路徑爲/debug/pprof/goroutine
。 -
heap
:查看活動對象的內存分配情況, 訪問路徑爲/debug/pprof/heap
。 -
mutex
:查看導致互斥鎖的競爭持有者的堆棧跟蹤,訪問路徑爲/debug/pprof/mutex
。 -
profile
:默認進行 30s 的 CPU Profiling,得到一個分析用的 profile 文件,訪問路徑爲/debug/pprof/profile
。 -
threadcreate
:查看創建新 OS 線程的堆棧跟蹤,訪問路徑爲/debug/pprof/threadcreate
。
最常用的 4 種 pprof 類型包括了堆分析 heap、協程棧分析 goroutine、CPU 佔用分析 profile、程序運行跟蹤信息 trace
3.2 交互式分析
下面以堆內存 (heap
) 分析爲示例:
# 進入交互命令
➜ go tool pprof http://127.0.0.1:6060/debug/pprof/heap?debug=1
Fetching profile over HTTP from http://127.0.0.1:6060/debug/pprof/heap?debug=1
Saved profile in /Users/liuqh/pprof/pprof.alloc_objects.alloc_space.inuse_objects.inuse_space.003.pb.gz
Type: inuse_space
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top # top 列出以fat列從大到小排序的序列
Showing nodes accounting for 11004.99kB, 100% of 11004.99kB total
Showing top 10 nodes out of 18
flat flat% sum% cum cum%
9975.41kB 90.64% 90.64% 9975.41kB 90.64% strings.(*Builder).grow
517.33kB 4.70% 95.35% 517.33kB 4.70% regexp/syntax.(*compiler).inst
512.25kB 4.65% 100% 512.25kB 4.65% regexp.onePassCopy
0 0% 100% 517.33kB 4.70% github.com/go-playground/validator/v10.init
0 0% 100% 512.25kB 4.65% github.com/go-playground/validator/v10.init.0
0 0% 100% 9975.41kB 90.64% main.testPprofHeap.func1
0 0% 100% 1029.58kB 9.36% regexp.Compile
0 0% 100% 1029.58kB 9.36% regexp.MustCompile
0 0% 100% 1029.58kB 9.36% regexp.compile
0 0% 100% 512.25kB 4.65% regexp.compileOnePass
1.flat|flat%|sum%|cum|cum%
針對不同的維度,展示代表的含義也有所區別,比如: 當分析維度是profile
時,flat
代表的是當前函數運行耗時,當分析維度是heap
,flat
代表的是當前函數佔用內存信息,但是他們都表示的是當前函數,依次類推。
-
flat
:只包含當前函數的X
信息,不包括其調用函數X
的信息。 -
flat%
:函數自身X
所佔的總比例。 -
sum%
:函數自身累積X
佔用總比例。 -
cum
:函數自身及其調用函數的累計總X
。 -
cum%
:函數自身及其調用函數的X
總比例。
X
根據維度不同代表含義也不同
當內存分析時, 代表所佔內存大小;
當 CPU 分析時, 代表所佔運行耗時;
2. 排查流程 (找出最佔內存的函數)
步驟一: 使用 top 排序,步驟二:使用 list,查看具體代碼。
# 使用top -cum,根據累計佔用內存排序
(pprof) top -cum
Showing nodes accounting for 11.37MB, 100% of 11.37MB total
flat flat% sum% cum cum%
0 0% 0% 11.37MB 100% main.testPprofHeap.func1
0 0% 0% 11.37MB 100% strings.(*Builder).Grow (inline)
11.37MB 100% 100% 11.37MB 100% strings.(*Builder).grow (inline)
0 0% 100% 11.37MB 100% strings.Repeat
# 使用list,查看具體代碼信息;
(pprof) list testPprofHeap
Total: 11.37MB
ROUTINE ======================== main.testPprofHeap.func1 in /Users/liuqh/ProjectItem/GoItem/go-pprof/main.go
0 11.37MB (flat, cum) 100% of Total
. . 31:func testPprofHeap() {
. . 32: go func() {
. . 33: var stringSlice []string
. . 34: for {
. . 35: time.Sleep(time.Second *2)
. 11.37MB 36: repeat := strings.Repeat("hello,world", 50000)
. . 37: stringSlice = append(stringSlice,repeat)
. . 38: fmt.Printf("time:%d length:%d \n",time.Now().Unix(),len(stringSlice))
. . 39: }
. . 40: }()
. . 41:}
踩坑: No source information for
在使用list x
, 報錯:No source information for
(pprof) list testPprofHeap
Total: 11.37MB
No source information for main.testPprofHeap.func1
(pprof) exit
解決方法:
# 使用下面格式替換: go tool pprof http://127.0.0.1:6060/debug/pprof/heap
➜ go tool pprof compileName http://127.0.0.1:6060/debug/pprof/heap
compileName: 指的是編譯後的程序文件
3.3 可視化分析
1. 安裝graphviz
# mac
brew install graphviz
# ubuntu
sudo apt install graphviz
更多安裝方法: https://graphviz.org/download
2. 方式一:web
# 在交互式命令行中輸入:web,會自動打開瀏覽器
(pprof) web
3. 方式二:-http :port
# 使用參數 -http :9090,直接在瀏覽器查詢
➜ go tool pprof -http :9090 http://127.0.0.1:6060/debug/pprof/heap
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/PJNY-qh2MY2H8yic6oNjjQ