Go pprof 認知到實踐

灯火消逝的码头發表於2024-05-08

快速開始

測試環境:go version go1.22.2 windows/amd64,原始碼開源在 https://github.com/oldme-git/teach-study/tree/master/golang/base/pprof

在正式開始之前,請確保安裝 graphviz,這一步不可省略,它可以協助 pprof 生成更直觀的資料分析圖。可以參考官方網站的安裝方法。

go 使用 runtime/pprof 包來對程式進行取樣,當然,還有另外一個包 net/http/pprof,這裡先按下不表。先來看一個 CPU 分析的例子:

package main

import (
	"math"
	"math/rand"
	"os"
	"runtime/pprof"
)

func main() {
	// 儲存 CPU 取樣資料
	file := "cpu.pprof"
	os.Remove(file)
	f, err := os.OpenFile(file, os.O_CREATE|os.O_RDWR, 0644)
	if err != nil {
		panic(err)
	}
	defer f.Close()

	// 開始取樣
	err = pprof.StartCPUProfile(f)
	if err != nil {
		panic(err)
	}
	defer pprof.StopCPUProfile()

	// 測試程式
	for i := 0; i < 1000; i++ {
		nums := genRandomNumbers(10000)
		for _, v := range nums {
			_ = math.Pow(float64(v), rand.Float64())
		}
	}
}

// 測試程式,生成一個隨機數切片
func genRandomNumbers(n int) []int {
	nums := make([]int, n)
	for i := 1; i < n; i++ {
		nums[i] = rand.Int() * n
	}
	return nums
}

這是一個很簡單的例子,執行 go run main.go 在當前目錄下生成一個 cpu.pprof 檔案。然後輸入命令 go tool pprof cpu.pprof 進入 pprof 的命令列中。

PS D:\project\teach-study\golang\base\pprof\cpu> go run  main.go        
PS D:\project\teach-study\golang\base\pprof\cpu> go tool pprof cpu.pprof
File: main.exe
Build ID: C:\Users\half\AppData\Local\Temp\go-build787417447\b001\exe\main.exe2024-05-08 11:13:12.7105156 +0800 CST
Type: cpu
Time: May 8, 2024 at 11:13am (CST)
Duration: 1.26s, Total samples = 1.07s (85.20%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)

pprof 命令有很多,可以輸入 help 檢視,不過一般常用的就兩個: topweb。輸入 top5 可以檢視前 5 的耗時呼叫。

// 輸入
top5

// 輸出
Showing nodes accounting for 700ms, 65.42% of 1070ms total
Showing top 5 nodes out of 69
      flat  flat%   sum%        cum   cum%
     210ms 19.63% 19.63%      210ms 19.63%  math.archLog
     180ms 16.82% 36.45%      180ms 16.82%  math.archExp
     160ms 14.95% 51.40%      670ms 62.62%  math.pow
      80ms  7.48% 58.88%       80ms  7.48%  internal/chacha8rand.block
      70ms  6.54% 65.42%       70ms  6.54%  math/rand.globalRand

來認識一下這五個指標:

flat 是我們最關注的指標,它代表自身耗時,不包含內部呼叫。
falt% 自身耗時相對於總耗時的百分比
cum 自身耗時加上內部函式呼叫的總耗時
cum% 自身耗時加上內部函式呼叫的總耗時相對於總耗時的百分比
sum% 前 N 行的 flat% 之和。對於上述例子的第四行 58.88=19.63+16.82+14.95+7.48

只是依賴文字無法很好的理解這些指標,我們可以使用 web 命令來生成更直觀的 svg 分析圖。輸入 web 命令後,會自動在瀏覽器開啟 svg

Go pprof 認知到實踐

svg 中的每個單元格包含了包名,函式名,flat, flat%, cum, cum%

Go pprof 認知到實踐

單元格顏色越紅,代表 cum 越大,反之越小;單元格越大,代表 flat 越大,反之越小。單元格之間的箭頭線代表呼叫鏈,線越粗代表消耗的更多的資源,反之亦然。帶有 inline 欄位表示該函式被內聯進了呼叫方(當作普通線處理就行)。

函式呼叫是存在一些固定開銷的,例如維護幀指標暫存器BP、棧溢位檢測等。因此,對於一些程式碼行比較少的函式,編譯器傾向於將它們在編譯期展開從而消除函式呼叫,這種行為就是內聯。

更多的 Web UI

透過 web 命令已經可以獲取很直觀的效能分析圖,我們還可以使用 -http 引數來啟用一個 web 服務,獲取更多的效能分析。輸入 exit 退出 pprof 命令介面,輸入命令:

go tool pprof -http=:7000 cpu.pprof

之後會自動在瀏覽器開啟 http://localhost:7000/ui/

Go pprof 認知到實踐

view 中可以使用火焰圖(Flame Graph),火焰圖有新舊兩種,可以根據線的長短和顏色判斷 CPU 耗時。其他的選項可以點點看看,不復雜,很容易就學會了。

Go pprof 認知到實踐

web 服務取樣

對於 web 服務的 pprof 取樣,我們可以使用基於 runtime/pprof 封裝的更便捷的 net/http/pprof 包。

package main

import (
	"fmt"
	"math"
	"math/rand"
	"net/http"
	_ "net/http/pprof"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		// 測試程式
		for i := 0; i < 1000; i++ {
			nums := genRandomNumbers(10000)
			for _, v := range nums {
				_ = math.Pow(float64(v), rand.Float64())
			}
		}
		fmt.Fprint(w, "Hello, world!")
	})
	http.ListenAndServe(":8080", nil)
}

// 測試程式,生成一個隨機數切片
func genRandomNumbers(n int) []int {
	nums := make([]int, n)
	for i := 1; i < n; i++ {
		nums[i] = rand.Int() * n
	}
	return nums
}

開啟 http://127.0.0.1:8080/debug/pprof/ ,可以看到 pprof 的實時取樣資料:

Go pprof 認知到實踐

這裡面一共有九個取樣資料:

allocs 檢視歷史累計的所有記憶體分配的取樣資料
block 檢視歷史累計的導致同步原語阻塞的堆疊跟蹤
cmdline 包含程序的完整命令列資訊,通常用於記錄程式啟動時的命令列引數
goroutine 實時檢視當前所有執行的 goroutines 堆疊跟蹤
heap 實時檢視活動物件的記憶體分配情況
mutex 檢視歷史累計的導致互斥鎖的競爭持有者的堆疊跟蹤
profile 進行 30s 的 CPU Profiling,瀏覽器會轉圈,30s 後下載一個分析用的 profile 檔案
threadcreate 檢視建立新 OS 執行緒的堆疊跟蹤
trace 程式執行 trace, 和其他樣本資料不同的是,這個需要使用 go tool trace 來分析。trace 是一種更詳細的效能分析工具,用於跟蹤程式的執行過程,包括函式呼叫、協程切換等。

預設是不追蹤 blockmutex 的,如果需要,在程式碼中加入這兩個:

runtime.SetBlockProfileRate(1) // 開啟 block  
runtime.SetMutexProfileFraction(1) // 開啟 mutex

這些資訊都是實時變化的,重新整理一下瀏覽器即可看見,但是這些資訊不易閱讀,我們可以把它們下載下來,使用 pprof 分析,以 allocs 為例:

// 下載 allocs 資料
curl -o allocs.pprof http://localhost:8080/debug/pprof/allocs
// pprof
go tool pprof .\allocs.pprof

非 Web 程式的其他取樣

在快速開始部分已經介紹了 CPU 取樣,對於其他取樣,可以參考這段程式碼:

package main

import (
	"math"
	"math/rand"
	"os"
	"runtime/pprof"
)

func main() {
	// 儲存 CPU 取樣資料
	file := "allocs.pprof"
	os.Remove(file)
	f, err := os.OpenFile(file, os.O_CREATE|os.O_RDWR, 0644)
	if err != nil {
		panic(err)
	}
	defer f.Close()

	// 測試程式
	for i := 0; i < 1000; i++ {
		nums := genRandomNumbers(10000)
		for _, v := range nums {
			_ = math.Pow(float64(v), rand.Float64())
		}
	}

	pprof.Lookup("allocs").WriteTo(f, 0)
}

// 測試程式,生成一個隨機數切片
func genRandomNumbers(n int) []int {
	nums := make([]int, n)
	for i := 1; i < n; i++ {
		nums[i] = rand.Int() * n
	}
	return nums
}

相關文章