pprof - 在現網場景怎麼用

轩脉刃發表於2024-04-08

如何使用 pprof 來定位現網 Golang 問題,已經是一名 Gopher 所需要掌握的必備技能了。我們在實際工作中也經常使用它來定位現網問題。網上有很多文章來描述 pprof 的使用,但是實際的線上使用場景,卻和各個文章的描述的多少有些差異。

比如網上大部分文章都會告訴你,使用命令列開啟 web 埠,在瀏覽器開啟 web 介面,接著告訴你如何在 web 介面檢視整個服務情況。但實際情況是,我們的線上服務基本都是跑在 centos 作業系統中的,由於安全問題,對外,對辦公網路不可能開放web 訪問埠,更無從透過 web 介面檢視服務情況,故此,web 介面在現網基本不可用。

網上還有一些文章告訴你,如何使用 go tool pprof 命令來互動式定位問題。但這裡隱藏的前提就是需要安裝 go 工具。但於我來說,這個是極其不方便的,我們的現網服務是使用映象安裝,而映象中只會安裝編譯好的業務二進位制檔案,並不會安裝 go 工具。除非我們使用運維許可權來安裝 go 命令。故此,我希望知道在無 go 工具的時候我能做些什麼。

我實際使用的現網環境是什麼樣的呢?- k8s 上執行無狀態的 centos7 作業系統的映象,映象中打包了所需要的二進位制命令,除此之外,只安裝了基礎的 vi,curl 命令。

那麼我尋求一種能不需要安裝,很快分析出 Golang 效能問題的辦法,故有此紀錄。

pprof埠

首先,first of all,巧婦難為無米之炊,這個米就是你在服務中開啟了一個 pprof 埠。我們的服務都是 web 型別的服務,提供 http,只需要引入一行程式碼即可。

import _ "net/http/pprof"

如果不是 web 服務的話,則除了引入上面的庫之外,還需要開啟一個 goroutine 來監聽服務。

go func() {
	log.Println(http.ListenAndServe("localhost:6060", nil))
}()

如此,在你的服務執行過程中,就能透過本地的 6060 埠來進行資料獲取。有米之後,才能烹煮大餐。

pprof 是一個套餐,裡面有不少餐食,而我們接下來要做的,則需要分析我們想定位的問題,比如我們想看下如下幾個問題:

  • 程序是否有goroutine 洩漏(對於 go 程式而言,有句話說的是,十次記憶體增長,九次都是由於 goroutine 洩漏)
  • 為什麼程式這麼佔用 cpu
  • 沒有 goroutine 洩漏,為什麼佔用了這麼多記憶體呢?

以上每個問題,我們都需要使用 pprof 套件中對應的工具來解決。

goroutine洩漏

我們最常犯的問題是 goroutine 洩漏。在 Golang 裡面,開啟一個協程是這麼容易,導致我們在很多地方會 go 一個函式出去,但是是否這些函式都按照預期回收了呢?我們這裡就需要用到 pprof 裡面的 goroutine 子命令來進行分析了。

在 go 中,分析 goroutine,我們並不需要安裝 go tool 工具,pprof 埠提供我們直接使用 curl 命令進行定位的能力。這也是我最常用的方法。

首先我們需要明確,goroutine 的分佈是一個狀態值,是可以直接列印出來的,當前程序中有多少 goroutine,分別是由哪些函式來建立的。並不需要進行時間段取樣。

curl -o goroutine.log localhost:6060/debug/pprof/goroutine?debug=1

這裡的 debug=1 是用來表示輸出的格式,它的值分別有 1/2和不填。

當填寫debug=1 的時候,輸出的是可讀性文字,是一種按照函式棧聚類分析的結果文字:

goroutine profile: total 2065
933 @ 0x4492d6 0x415e4c 0x4158b8 0x153ddf1 0x159a61e 0x47aa41
#       0x153ddf0       github.com/Shopify/sarama.(*partitionProducer).dispatch+0x1b0   /root/go/pkg/mod/github.com/!shopify/sarama@v1.29.1/async_producer.go:545
#       0x159a61d       github.com/Shopify/sarama.withRecover+0x3d                      /root/go/pkg/mod/github.com/!shopify/sarama@v1.29.1/utils.go:43

194 @ 0x4492d6 0x415e4c 0x4158b8 0x153f385 0x159a61e 0x47aa41
#       0x153f384       github.com/Shopify/sarama.(*asyncProducer).newBrokerProducer.func1+0x64 /root/go/pkg/mod/github.com/!shopify/sarama@v1.29.1/async_producer.go:694
#       0x159a61d       github.com/Shopify/sarama.withRecover+0x3d                              /root/go/pkg/mod/github.com/!shopify/sarama@v1.29.1/utils.go:43

如以上文字,說明了當前程序總共啟動了 2065 個 goroutine,其中列出了排名靠前的 2 個堆疊:

  • 第一個堆疊是透過 github.com/Shopify/sarama.(*partitionProducer).dispatch 建立了 933 個 goroutine,在預期內
  • 第二個堆疊透過 github.com/Shopify/sarama.(*asyncProducer).newBrokerProducer.func1 建立了 194 個 goroutine,在預期內

這樣分析前幾個佔用 goroutine 數最多的堆疊,就大概能知道業務程序是否有 goroutine 洩漏了。debug=1 也是我最常用的分析方式。

填寫 debug=2 的時候,輸出的也是可讀性文字,但它的輸出是按照 goroutine 分類的結果文字:

goroutine 34890598 [running]:
runtime/pprof.writeGoroutineStacks({0x2917520, 0xc002716c40})
        /usr/go/src/runtime/pprof/pprof.go:693 +0x70
runtime/pprof.writeGoroutine({0x2917520, 0xc002716c40}, 0x0)
        /usr/go/src/runtime/pprof/pprof.go:682 +0x2b
runtime/pprof.(*Profile).WriteTo(0x21ae7e0, {0x2917520, 0xc002716c40}, 0xc)
        /usr/go/src/runtime/pprof/pprof.go:331 +0x14b
net/http/pprof.handler.ServeHTTP({0xc00b075ea1, 0xc000153000}, {0x2939d10, 0xc002716c40}, 0xc00b075e94)
        /usr/go/src/net/http/pprof/pprof.go:253 +0x49a
net/http/pprof.Index({0x2939d10, 0xc002716c40}, 0xc007f8ce00)
        /usr/go/src/net/http/pprof/pprof.go:371 +0x12e
net/http.HandlerFunc.ServeHTTP(0xc00428aa30, {0x2939d10, 0xc002716c40}, 0xc00b075eab)
        /usr/go/src/net/http/server.go:2047 +0x2f
...

goroutine 1 [select, 6426 minutes]:
git.code.oa.com/my-go/server.(*Server).Serve(0xc000014cb0)
        /root/go/pkg/mod/my-go@v0.1.1/server/serve_unix.go:46 +0x3ae
main.main()
        /data/__qci/root-workspaces/__qci-pipeline-251750-1/main.go:111 +0x4d6

goroutine 19 [syscall, 702 minutes]:
syscall.Syscall6(0xe8, 0x7, 0xc00020fbec, 0x7, 0xffffffffffffffff, 0x0, 0x0)
        /usr/go/src/syscall/asm_linux_amd64.s:43 +0x5
golang.org/x/sys/unix.EpollWait(0x2, {0xc00020fbec, 0x2, 0xc0091c98c0}, 0xc00020fcb4)
        /root/go/pkg/mod/golang.org/x/sys@v0.0.0-20220811171246-fbc7d0a398ab/unix/zsyscall_linux_amd64.go:56 +0x58
github.com/fsnotify/fsnotify.(*fdPoller).wait(0xc000122300)
        /root/go/pkg/mod/github.com/fsnotify/fsnotify@v1.4.9/inotify_poller.go:86 +0x7d
github.com/fsnotify/fsnotify.(*Watcher).readEvents(0xc00010e690)
        /root/go/pkg/mod/github.com/fsnotify/fsnotify@v1.4.9/inotify.go:192 +0x2b0
created by github.com/fsnotify/fsnotify.NewWatcher
        /root/go/pkg/mod/github.com/fsnotify/fsnotify@v1.4.9/inotify.go:59 +0x1c7

這個文字非常長,它把每個 goroutine 的堆疊資訊,執行狀態,執行時長都列了出來。如上圖,列了 3 個 gorouine ,

  • 第一個 gorourine 34890598 是執行狀態
  • 第二個 goroutine 1 是在 select 阻塞狀態,已經執行了 6426 分鐘了。從堆疊可以看出來,這個 gorourine 是我們web 服務的主要 goroutine
  • 第三個 goroutine 19 是在系統呼叫狀態,已經執行了 702 分鐘了。透過堆疊,我們也不難看出它正處在 epollwait 的邏輯中

debug=2 的方式最有用的就是 goroutine 的執行時長和更詳細的堆疊資訊,在使用 debug=1 定位出可疑堆疊的時候,用堆疊的函式去 debug=2 中找,能大概看出來它已經啟動多久,更具體的堆疊有哪些,當前是卡在哪個系統函式中,能更快定位問題。

這兩種 debug 的值是可以直接在控制檯透過 curl 和 vi進行檢視的,對於現網非常方便的。如果不指定 debug ,輸出的 goroutine.log 開啟就是一種二進位制亂碼,它就只能透過 go tool pprof 工具開啟,這樣在沒有go tool 的環境中,就需要想辦法安裝 go 工具,或者下載到有 go 工具的環境才能閱讀了。當然使用 go tool 工具很麻煩,但是它能看出更多的資訊。

curl -o goroutine.log localhost:11014/debug/pprof/goroutine
go tool pprof goroutine.log

進入控制檯,我們最常使用的就是 top [100] 命令,比如 top 100

(pprof) top100
Showing nodes accounting for 2027, 99.75% of 2032 total
Dropped 204 nodes (cum <= 10)
      flat  flat%   sum%        cum   cum%
      2027 99.75% 99.75%       2027 99.75%  runtime.gopark
         0     0% 99.75%        186  9.15%  bufio.(*Reader).Peek
         0     0% 99.75%         19  0.94%  bufio.(*Reader).Read
         0     0% 99.75%        187  9.20%  bufio.(*Reader).fill
         0     0% 99.75%         32  1.57%  my-go/kafka.(*singleConsumerHandler).ConsumeClaim
         0     0% 99.75%         16  0.79%  my-go/transport.(*serverTransport).servePacket
         0     0% 99.75%         16  0.79%  my-go/transport.(*serverTransport).serveUDP
         0     0% 99.75%         16  0.79%  github.com/Shopify/sarama.(*Broker).Fetch
         0     0% 99.75%         16  0.79%  github.com/Shopify/sarama.(*Broker).readFull
         0     0% 99.75%         62  3.05%  github.com/Shopify/sarama.(*Broker).responseReceiver
         0     0% 99.75%         16  0.79%  github.com/Shopify/sarama.(*Broker).sendAndReceive
         0     0% 99.75%        168  8.27%  github.com/Shopify/sarama.(*asyncProducer).newBrokerProducer.func1
         0     0% 99.75%         16  0.79%  github.com/Shopify/sarama.(*brokerConsumer).fetchNewMessages
         0     0% 99.75%         17  0.84%  github.com/Shopify/sarama.(*brokerConsumer).subscriptionConsumer
         0     0% 99.75%         17  0.84%  github.com/Shopify/sarama.(*brokerConsumer).subscriptionManager
         0     0% 99.75%        168  8.27%  github.com/Shopify/sarama.(*brokerProducer).run
         0     0% 99.75%         16  0.79%  github.com/Shopify/sarama.(*bufConn).Read
         0     0% 99.75%         32  1.57%  github.com/Shopify/sarama.(*consumerGroupSession).consume
         0     0% 99.75%         33  1.62%  github.com/Shopify/sarama.(*consumerGroupSession).consume.func1
         0     0% 99.75%         33  1.62%  github.com/Shopify/sarama.(*consumerGroupSession).consume.func2
         0     0% 99.75%         33  1.62%  github.com/Shopify/sarama.(*partitionConsumer).dispatcher
         0     0% 99.75%         33  1.62%  github.com/Shopify/sarama.(*partitionConsumer).responseFeeder
         0     0% 99.75%        933 45.92%  github.com/Shopify/sarama.(*partitionProducer).dispatch
         0     0% 99.75%         33  1.62%  github.com/Shopify/sarama.newConsumerGroupClaim.func1
         0     0% 99.75%         33  1.62%  github.com/Shopify/sarama.newConsumerGroupSession.func1
         0     0% 99.75%         32  1.57%  github.com/Shopify/sarama.newConsumerGroupSession.func2
         0     0% 99.75%       1453 71.51%  github.com/Shopify/sarama.withRecover

這個 top 命令其實帶的資訊就值得好好琢磨琢磨了。

Showing nodes accounting for 2027, 99.75% of 2032 total 這裡的 top 100 已經展示了所有 2032 個 goroutine 中的 2027 個了,佔比 99.75%。所以透過這個,我們就能知道,top 100 就已經很夠分析了,可以不同再擴大 top 的個數了。

但是即使是 top100,我們全部展示也太大了,所以我們需要捨棄一些函式,Dropped 204 nodes (cum <= 10) 告訴我們,以下列表捨棄了 cum <= 10 的函式,具體 cum 值是什麼意思呢,下面一起說。

flat, sum, cum 這三個值在 pprof 的各個工具中都會出現,要知道他們的意思才能進一步分析:

  • flat 代表我這個函式自己佔用的資源(這裡就是 goroutine 數)
  • sum 則代表我這個函式和呼叫我的函式佔用的資源(這裡就是 gorourine 數)
  • cum 則代表我這個函式和我呼叫的函式佔用的資源(這裡就是 gouroutine 數)。

上栗子:我現在有個呼叫鏈,funcA -> funcB -> funcC。且在 funcA/B/C 中,除了函式呼叫之外,他們函式自身也會各自建立 goroutine,我們假設 funcA/B/C 各自函式建立的 goroutine 數為 1/2/3。

那麼對於 funcB 而言,它的 flat 指的是 funcB 這個函式自己建立的數量,為 2。而 sum 表示的是 funB 這個函式,及呼叫它的函式 funcA 共同建立的 goroutine 的數量,為 1+2 =3。而 cum 表示的是 funB 和它呼叫的函式 funcC 建立的 goroutine 的數量,為 2+3 = 5。

而每個函式的這三個指標在所有函式中的佔比就為三個指標對應的百分比:flat% sum% cum%。

理解了這些,我們就能理解為什麼在第一列的gopark 是那樣的數值了。

flat  flat%   sum%        cum   cum%
2027 99.75% 99.75%       2027 99.75%  runtime.gopark

這是一個最底層函式,基本所有的 goroutine 建立之後都會呼叫它,所以它自身建立的 goroutine 數為 flat: 2027,且它沒有下層呼叫,所以它的cum 也為 2027。

再看一個第二列的數值:

flat  flat%   sum%        cum   cum%
0     0%     99.75%        186  9.15%  bufio.(*Reader).Peek

bufio.(*Reader).Peek 這個函式自身不建立 goroutine(flat = 0),但是呼叫它的函式建立的 goroutine 佔比 ( 99.75% ),它的下層呼叫建立了 186 個 goroutine,佔比 9.15%。

所以我們要分析 goroutine 洩漏的時候要看哪個值呢?- 對的,cum。

找到 cum 佔比最大的函式,這裡一般我們都會查詢非系統呼叫函式,(因為系統呼叫函式不大可能有問題),那麼我們就知道我們的哪個應用函式存在 goroutine 洩漏。

這裡另外說下,有的文章說可以下一步用 list + 函式名 的方式來列出最佔用資源的行數。行,當然行,但是它的使用情景更為苛刻了 - 需要在現網機器安裝原始碼+依賴庫。而這個在現網環境基本是不可能的,所以這個list 命令在實際分析中我自己是很少用到的。

cpu問題

如果我們是一個 cpu 暴漲的問題,我們就需要使用pprof 套件中的 profile 工具來定位出哪個函式佔用 cpu 比較多。

這裡我們需要明確一下:

我們說是要定位一個程式的 cpu 消耗問題,但是這裡其實隱藏了一個時間段概念,即:“我們要定位什麼時間段內的 cpu 消耗”? - 這裡當然無法定位過去時間的 cpu 消耗,我們只能定位從執行時間開始一段時間內的 cpu 消耗,即“從現在開始的 30s 作為取樣樣本,來分析這段時間內的 cpu 耗時分佈”。所以我們需要給 pprof 埠下命令,來定位抓取 30s 內的消耗,同樣,我們使用 curl 命令即可。

curl -o profile.log localhost:6060/debug/pprof/profile?seconds=30

這裡的兩個引數,seconds - 指定樣本時間,很好理解。而這裡輸出的 profile.log 開啟是一種二進位制亂碼,profile 是沒有 debug 引數的,它只能透過 go tool pprof 工具開啟。如果我們安裝 go 工具,或者下載 profile.log 到有 go 工具的環境,我們才能閱讀。- 這裡就是比較麻煩的一點了。

不過所幸,閱讀的方法同 goroutine 的 top 命令類似,唯一不同的是,這裡的資源換成了函式消耗的 cpu 時間。

(pprof) top100
Showing nodes accounting for 4.26s, 79.92% of 5.33s total
Dropped 370 nodes (cum <= 0.03s)
Showing top 100 nodes out of 395
      flat  flat%   sum%        cum   cum%
     0.49s  9.19%  9.19%      0.73s 13.70%  runtime.scanobject
     0.43s  8.07% 17.26%      0.43s  8.07%  runtime.futex
     0.32s  6.00% 23.26%      0.35s  6.57%  syscall.Syscall
     0.23s  4.32% 27.58%      0.53s  9.94%  runtime.mallocgc
     0.19s  3.56% 31.14%      0.19s  3.56%  runtime.epollwait
     0.13s  2.44% 33.58%      0.13s  2.44%  runtime.greyobject
     0.13s  2.44% 36.02%      0.14s  2.63%  runtime.heapBitsSetType
     0.13s  2.44% 38.46%      0.13s  2.44%  runtime.memmove
     0.12s  2.25% 40.71%      0.12s  2.25%  runtime.findObject
     0.12s  2.25% 42.96%      0.12s  2.25%  runtime.usleep
     0.10s  1.88% 44.84%      0.80s 15.01%  runtime.findrunnable
     0.10s  1.88% 46.72%      0.26s  4.88%  runtime.stealWork
     0.08s  1.50% 48.22%      0.08s  1.50%  [libc-2.28.so]
     0.08s  1.50% 49.72%      0.11s  2.06%  runtime.mapaccess2
     0.08s  1.50% 51.22%      0.08s  1.50%  runtime.memclrNoHeapPointers
     0.06s  1.13% 52.35%      0.11s  2.06%  go.uber.org/zap/zapcore.(*jsonEncoder).tryAddRuneSelf
     0.05s  0.94% 53.28%      0.19s  3.56%  google.golang.org/protobuf/internal/impl.(*MessageInfo).unmarshalPointer
     0.05s  0.94% 54.22%      0.06s  1.13%  runtime.lock2
     0.05s  0.94% 55.16%      0.15s  2.81%  runtime.mapassign_faststr
     0.05s  0.94% 56.10%      0.05s  0.94%  runtime.step

如何分析?如果你看了 goroutine 那節,就知道這是一個套路了,如果沒有看,往上翻。 - 1 看 cum 值,2 過濾出非系統函式呼叫,第一個佔用最大的應用函式,就是目標函式了,然後分析程式碼,有沒有什麼 for 死迴圈,有沒有什麼耗 cpu 的行為,就是它了。

記憶體問題

在沒有 goroutine 洩漏的前提下,記憶體為什麼佔用這麼大呢?我們需要 pprof 套件中的兩個工具,allocs 和 heap。

為什麼有兩個呢?它們兩個的差別就是allocs 代表的是某個函式歷史建立的所有的記憶體(包括已經回收了的),而 heap 代表的是當前活躍的記憶體佔用物件。所以我們能看到 allocs 中的數值會比 heap 大了非常多,一般我們使用 heap 比較多。

他們兩個命令都有 debug 引數,但是 debug 引數只能為 1 或者沒有。同 goroutine 命令一樣,沒有 debug 引數就只能透過 go tool 來分析,會麻煩些。

我們這裡分析一個下 debug=1 的可讀性文字:

curl -o allocs1.log localhost:6606/debug/pprof/allocs?debug=1

這裡說一下,allocs 和 heap 兩個命令使用 debug=1 的引數,顯示的內容其實都是一樣的,可讀性文字里面不僅包含了當前活躍的記憶體資訊,也包含了歷史建立的記憶體資訊。比如下面這個展示:

heap profile: 169: 18537048 [35672473: 126318298296] @ heap/1048576
1: 1810432 [1: 1810432] @ 0x41dbee 0x41d99c 0x1b75345 0x456203 0x456151 0x456151 0x456151 0x448ec6 0x47aa41
#       0x1b75344       github.com/mozillazg/go-pinyin.init+0x3c4       /root/go/pkg/mod/github.com/mozillazg/go-pinyin@v0.18.0/pinyin_dict.go:5
#       0x456202        runtime.doInit+0x122                            /usr/go/src/runtime/proc.go:6498
#       0x456150        runtime.doInit+0x70                             /usr/go/src/runtime/proc.go:6475
#       0x456150        runtime.doInit+0x70                             /usr/go/src/runtime/proc.go:6475
#       0x456150        runtime.doInit+0x70                             /usr/go/src/runtime/proc.go:6475
#       0x448ec5        runtime.main+0x1e5                              /usr/go/src/runtime/proc.go:238

第一行 169: 18537048 [35672473: 126318298296] 告訴我們,這個程序目前活躍 169 個物件,佔用位元組數18537048,歷史有過 35672473 個物件,佔用位元組數 126318298296。 1048576 表示記憶體取樣的頻率,預設取樣頻率是512kb 。這裡取樣頻率是兩倍的記憶體取樣頻率(1024 x 1024)。

第二行 1: 1810432 [1: 1810432] 告訴我們,下面這個堆疊活躍 1 個物件,佔用位元組數 1810432,歷史只有過 1 個物件,佔用位元組數 1810432。

但是實話說,分析記憶體如果用這個 curl 的方式,會看的很累,因為它的排序並沒有任何太多意義,比如佔用最大記憶體的,可能是非常合理的,要看到後面幾名才能知道哪個佔用記憶體大不合理。

還是透過 go tool 工具來檢視top 更為快捷:

以下是 allocs 命令的 top

(pprof) top10
Showing nodes accounting for 8273GB, 47.42% of 17444.69GB total
Dropped 2963 nodes (cum <= 87.22GB)
Showing top 10 nodes out of 267
      flat  flat%   sum%        cum   cum%
 1948.96GB 11.17% 11.17%  1948.96GB 11.17%  go.uber.org/zap/buffer.(*Buffer).AppendByte (inline)
 1260.22GB  7.22% 18.40%  1260.22GB  7.22%  my-go/codec.MetaData.Clone
  895.47GB  5.13% 23.53%  3649.40GB 20.92%  go.uber.org/zap/zapcore.(*ioCore).Write
  894.80GB  5.13% 28.66%   987.35GB  5.66%  google.golang.org/protobuf/internal/encoding/text.(*Encoder).WriteName (inline)
  813.76GB  4.66% 33.32%   813.76GB  4.66%  go.uber.org/zap/internal/bufferpool.init.func1
  780.93GB  4.48% 37.80%  3200.55GB 18.35%  fmt.Sprintf

以下是 heap 命令的 top

Showing nodes accounting for 47.06MB, 50.44% of 93.30MB total
Showing top 10 nodes out of 487
      flat  flat%   sum%        cum   cum%
   12.93MB 13.86% 13.86%    12.93MB 13.86%  google.golang.org/protobuf/internal/strs.(*Builder).AppendFullName
    9.06MB  9.71% 23.57%    17.50MB 18.75%  google.golang.org/protobuf/internal/filedesc.(*Message).unmarshalFull
    6.11MB  6.54% 30.12%     6.11MB  6.54%  github.com/rcrowley/go-metrics.newExpDecaySampleHeap
    3.50MB  3.75% 33.87%     6.50MB  6.97%  my-go/pkg/model/pb.v2ServiceInstancesToV1
    3.01MB  3.22% 37.09%     3.01MB  3.22%  github.com/Shopify/sarama.(*asyncProducer).newPartitionProducer
       3MB  3.22% 40.31%        3MB  3.22%  my-go/pkg/model/pb.v2StringToV1WrapperString (inline)

從這裡也能看出,alloc 的數值比 heap 大了不少,相比之下,heap 命令對於分析當前服務記憶體佔用更有作用。

其他

pprof 套件中還有很多工具,有哪些工具呢?不用費心去記,直接 curl 也能看出來。

curl http://localhost:6606/debug/pprof/

輸出的 html 有如下的有用資訊:

<tr><td>204775</td><td><a href='allocs?debug=1'>allocs</a></td></tr>
<tr><td>0</td><td><a href='block?debug=1'>block</a></td></tr>
<tr><td>0</td><td><a href='cmdline?debug=1'>cmdline</a></td></tr>
<tr><td>1882</td><td><a href='goroutine?debug=1'>goroutine</a></td></tr>
<tr><td>204775</td><td><a href='heap?debug=1'>heap</a></td></tr>
<tr><td>0</td><td><a href='mutex?debug=1'>mutex</a></td></tr>
<tr><td>0</td><td><a href='profile?debug=1'>profile</a></td></tr>
<tr><td>18</td><td><a href='threadcreate?debug=1'>threadcreate</a></td></tr>
<tr><td>0</td><td><a href='trace?debug=1'>trace</a></td></tr>

<li><div class=profile-name>allocs: </div> A sampling of all past memory allocations</li>
<li><div class=profile-name>block: </div> Stack traces that led to blocking on synchronization primitives</li>
<li><div class=profile-name>cmdline: </div> The command line invocation of the current program</li>
<li><div class=profile-name>goroutine: </div> Stack traces of all current goroutines</li>
<li><div class=profile-name>heap: </div> A sampling of memory allocations of live objects. You can specify the gc GET parameter to run GC before taking the heap sample.</li>
<li><div class=profile-name>mutex: </div> Stack traces of holders of contended mutexes</li>
<li><div class=profile-name>profile: </div> CPU profile. You can specify the duration in the seconds GET parameter. After you get the profile file, use the go tool pprof command to investigate the profile.</li>
<li><div class=profile-name>threadcreate: </div> Stack traces that led to the creation of new OS threads</li>
<li><div class=profile-name>trace: </div> A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace.</li>

讓 Claude 幫我們解釋下:

這些是 Go (golang) pprof 包提供的不同的分析工具,每個工具都有特定的目的,用於分析和除錯 Go 應用程式的效能問題。以下是每個工具的功能:

  1. allocs: 這個工具提供了過去所有記憶體分配的取樣,可用於識別記憶體洩漏或最佳化應用程式的記憶體使用。
  2. block: 這個工具提供了阻塞在同步原語(如互斥鎖或通道)上的 goroutine 的堆疊跟蹤,可幫助識別程式碼中潛在的瓶頸或死鎖問題。
  3. cmdline: 這個工具簡單地返回當前程式的命令列呼叫,可用於瞭解程式是如何啟動的。
  4. goroutine: 這個工具提供了所有當前 goroutine 的堆疊跟蹤,可幫助瞭解應用程式的整體併發性,並識別可能存在問題的 goroutine。
  5. heap: 這個工具提供了活動物件記憶體分配的取樣,可用於識別記憶體使用模式和潛在的記憶體洩漏。
  6. mutex: 這個工具提供了持有爭用互斥鎖的 goroutine 的堆疊跟蹤,可幫助識別應用程式中潛在的併發問題或瓶頸。
  7. profile: 這個工具生成 CPU 效能分析,可用於識別應用程式中 CPU 密集型的效能問題。您可以使用 seconds GET 引數指定分析的持續時間。
  8. threadcreate: 這個工具提供了導致新的作業系統執行緒建立的堆疊跟蹤,可用於瞭解應用程式的整體併發性和資源利用情況。
  9. trace: 這個工具生成應用程式執行的追蹤,可用於分析應用程式的整體效能,包括 goroutine 排程、I/O 和同步事件。您可以使用 seconds GET 引數指定追蹤的持續時間。

這些工具對於理解和最佳化 Go 應用程式的效能非常有價值,特別是在處理複雜的併發系統或記憶體密集型工作負載時。

其他幾個工具我基本上很少使用了,等有用的時候再記錄吧,或許一直也用不到,那麼正應了那句古話:書到不用時方嫌多。

參考

https://mp.weixin.qq.com/s/_MWtrXYWm5pzT8Rt3r_YDQ
https://www.cnblogs.com/hobbybear/p/17292713.html
https://go.dev/doc/diagnostics
https://jvns.ca/blog/2017/09/24/profiling-go-with-pprof/
https://segmentfault.com/a/1190000016412013
https://segmentfault.com/a/1190000019222661
https://blog.wolfogre.com/posts/go-ppof-practice/

相關文章