Go 常用包: 調試利器 pprof 使用 -上-

  1. 介紹

Go語言中的pprof指對於指標或特徵的分析(Profiling),通過分析不僅可以查找到程序中的錯誤(內存泄漏、race 衝突、協程泄漏),也能對程序進行優化(例如 CPU 利用率不足)。

由於Go語言運行時的指標不對外暴露,因此有標準庫net/http/pprofruntime/pprof用於與外界交互。其中net/http/pprof提供了一種通過http訪問的便利方式,用於用戶調試和獲取樣本特徵數據。

對特徵文件進行分析要依賴谷歌推出的分析工具pprof,該工具在 Go 安裝時即存在。

  1. 收集樣本

在通過pprof進行特徵分析時,需要執行兩個步驟:收集樣本和分析樣本

pprof 採樣數據主要有三種獲取方式:

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 文件

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)
  1. 分析樣本

pprof提供了很多維度的分析,若想分析某一個維度信息,可直接使用go tool pprof http://ip:port/debug/pprof/維度,進入交互式分析。

3.1 分析維度

最常用的 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代表的是當前函數運行耗時,當分析維度是heapflat代表的是當前函數佔用內存信息,但是他們都表示的是當前函數,依次類推。

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