您的位置:首頁 > 軟件教程 > 教程 > 參考

參考

來源:好特整理 | 時(shí)間:2024-04-08 11:47:52 | 閱讀:72 |  標(biāo)簽:   | 分享到:

如何使用 pprof 來定位現(xiàn)網(wǎng) Golang 問題,已經(jīng)是一名 Gopher 所需要掌握的必備技能了。我們在實(shí)際工作中也經(jīng)常使用它來定位現(xiàn)網(wǎng)問題。網(wǎng)上有很多文章來描述 pprof 的使用,但是實(shí)際的線上使用場景,卻和各個(gè)文章的描述的多少有些差異。 比如網(wǎng)上大部分文章都會(huì)告訴你,使用命令行打開 we

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

比如網(wǎng)上大部分文章都會(huì)告訴你,使用命令行打開 web 端口,在瀏覽器打開 web 界面,接著告訴你如何在 web 界面查看整個(gè)服務(wù)情況。但實(shí)際情況是,我們的線上服務(wù)基本都是跑在 centos 操作系統(tǒng)中的,由于安全問題,對外,對辦公網(wǎng)絡(luò)不可能開放web 訪問端口,更無從通過 web 界面查看服務(wù)情況,故此,web 界面在現(xiàn)網(wǎng)基本不可用。

網(wǎng)上還有一些文章告訴你,如何使用 go tool pprof 命令來交互式定位問題。但這里隱藏的前提就是需要安裝 go 工具。但于我來說,這個(gè)是極其不方便的,我們的現(xiàn)網(wǎng)服務(wù)是使用鏡像安裝,而鏡像中只會(huì)安裝編譯好的業(yè)務(wù)二進(jìn)制文件,并不會(huì)安裝 go 工具。除非我們使用運(yùn)維權(quán)限來安裝 go 命令。故此,我希望知道在無 go 工具的時(shí)候我能做些什么。

我實(shí)際使用的現(xiàn)網(wǎng)環(huán)境是什么樣的呢?- k8s 上運(yùn)行無狀態(tài)的 centos7 操作系統(tǒng)的鏡像,鏡像中打包了所需要的二進(jìn)制命令,除此之外,只安裝了基礎(chǔ)的 vi,curl 命令。

那么我尋求一種能不需要安裝,很快分析出 Golang 性能問題的辦法,故有此紀(jì)錄。

pprof端口

首先,first of all,巧婦難為無米之炊,這個(gè)米就是你在服務(wù)中開啟了一個(gè) pprof 端口。我們的服務(wù)都是 web 類型的服務(wù),提供 http,只需要引入一行代碼即可。

import _ "net/http/pprof"

如果不是 web 服務(wù)的話,則除了引入上面的庫之外,還需要開啟一個(gè) goroutine 來監(jiān)聽服務(wù)。

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

如此,在你的服務(wù)運(yùn)行過程中,就能通過本地的 6060 端口來進(jìn)行數(shù)據(jù)獲取。有米之后,才能烹煮大餐。

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

  • 進(jìn)程是否有g(shù)oroutine 泄漏(對于 go 程序而言,有句話說的是,十次內(nèi)存增長,九次都是由于 goroutine 泄漏)
  • 為什么程序這么占用 cpu
  • 沒有 goroutine 泄漏,為什么占用了這么多內(nèi)存呢?

以上每個(gè)問題,我們都需要使用 pprof 套件中對應(yīng)的工具來解決。

goroutine泄漏

我們最常犯的問題是 goroutine 泄漏。在 Golang 里面,開啟一個(gè)協(xié)程是這么容易,導(dǎo)致我們在很多地方會(huì) go 一個(gè)函數(shù)出去,但是是否這些函數(shù)都按照預(yù)期回收了呢?我們這里就需要用到 pprof 里面的 goroutine 子命令來進(jìn)行分析了。

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

首先我們需要明確,goroutine 的分布是一個(gè)狀態(tài)值,是可以直接打印出來的,當(dāng)前進(jìn)程中有多少 goroutine,分別是由哪些函數(shù)來創(chuàng)建的。并不需要進(jìn)行時(shí)間段采樣。

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

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

當(dāng)填寫debug=1 的時(shí)候,輸出的是可讀性文本,是一種按照函數(shù)棧聚類分析的結(jié)果文本:

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/[email protected]/async_producer.go:545
#       0x159a61d       github.com/Shopify/sarama.withRecover+0x3d                      /root/go/pkg/mod/github.com/!shopify/[email protected]/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/[email protected]/async_producer.go:694
#       0x159a61d       github.com/Shopify/sarama.withRecover+0x3d                              /root/go/pkg/mod/github.com/!shopify/[email protected]/utils.go:43

如以上文本,說明了當(dāng)前進(jìn)程總共啟動(dòng)了 2065 個(gè) goroutine,其中列出了排名靠前的 2 個(gè)堆棧:

  • 第一個(gè)堆棧是通過 github.com/Shopify/sarama.(*partitionProducer).dispatch 創(chuàng)建了 933 個(gè) goroutine,在預(yù)期內(nèi)
  • 第二個(gè)堆棧通過 github.com/Shopify/sarama.(*asyncProducer).newBrokerProducer.func1 創(chuàng)建了 194 個(gè) goroutine,在預(yù)期內(nèi)

這樣分析前幾個(gè)占用 goroutine 數(shù)最多的堆棧,就大概能知道業(yè)務(wù)進(jìn)程是否有 goroutine 泄漏了。debug=1 也是我最常用的分析方式。

填寫 debug=2 的時(shí)候,輸出的也是可讀性文本,但它的輸出是按照 goroutine 分類的結(jié)果文本:

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/[email protected]/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/[email protected]/unix/zsyscall_linux_amd64.go:56 +0x58
github.com/fsnotify/fsnotify.(*fdPoller).wait(0xc000122300)
        /root/go/pkg/mod/github.com/fsnotify/[email protected]/inotify_poller.go:86 +0x7d
github.com/fsnotify/fsnotify.(*Watcher).readEvents(0xc00010e690)
        /root/go/pkg/mod/github.com/fsnotify/[email protected]/inotify.go:192 +0x2b0
created by github.com/fsnotify/fsnotify.NewWatcher
        /root/go/pkg/mod/github.com/fsnotify/[email protected]/inotify.go:59 +0x1c7

這個(gè)文本非常長,它把每個(gè) goroutine 的堆棧信息,運(yùn)行狀態(tài),運(yùn)行時(shí)長都列了出來。如上圖,列了 3 個(gè) gorouine ,

  • 第一個(gè) gorourine 34890598 是運(yùn)行狀態(tài)
  • 第二個(gè) goroutine 1 是在 select 阻塞狀態(tài),已經(jīng)運(yùn)行了 6426 分鐘了。從堆?梢钥闯鰜,這個(gè) gorourine 是我們web 服務(wù)的主要 goroutine
  • 第三個(gè) goroutine 19 是在系統(tǒng)調(diào)用狀態(tài),已經(jīng)運(yùn)行了 702 分鐘了。通過堆棧,我們也不難看出它正處在 epollwait 的邏輯中

debug=2 的方式最有用的就是 goroutine 的運(yùn)行時(shí)長和更詳細(xì)的堆棧信息,在使用 debug=1 定位出可疑堆棧的時(shí)候,用堆棧的函數(shù)去 debug=2 中找,能大概看出來它已經(jīng)啟動(dòng)多久,更具體的堆棧有哪些,當(dāng)前是卡在哪個(gè)系統(tǒng)函數(shù)中,能更快定位問題。

這兩種 debug 的值是可以直接在控制臺(tái)通過 curl 和 vi進(jìn)行查看的,對于現(xiàn)網(wǎng)非常方便的。如果不指定 debug ,輸出的 goroutine.log 打開就是一種二進(jìn)制亂碼,它就只能通過 go tool pprof 工具打開,這樣在沒有g(shù)o tool 的環(huán)境中,就需要想辦法安裝 go 工具,或者下載到有 go 工具的環(huán)境才能閱讀了。當(dāng)然使用 go tool 工具很麻煩,但是它能看出更多的信息。

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

進(jìn)入控制臺(tái),我們最常使用的就是 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

這個(gè) top 命令其實(shí)帶的信息就值得好好琢磨琢磨了。

Showing nodes accounting for 2027, 99.75% of 2032 total 這里的 top 100 已經(jīng)展示了所有 2032 個(gè) goroutine 中的 2027 個(gè)了,占比 99.75%。所以通過這個(gè),我們就能知道,top 100 就已經(jīng)很夠分析了,可以不同再擴(kuò)大 top 的個(gè)數(shù)了。

但是即使是 top100,我們?nèi)空故疽蔡罅,所以我們需要舍棄一些函?shù), Dropped 204 nodes (cum <= 10) 告訴我們,以下列表舍棄了 cum <= 10 的函數(shù),具體 cum 值是什么意思呢,下面一起說。

flat, sum, cum 這三個(gè)值在 pprof 的各個(gè)工具中都會(huì)出現(xiàn),要知道他們的意思才能進(jìn)一步分析:

  • flat 代表我這個(gè)函數(shù)自己占用的資源(這里就是 goroutine 數(shù))
  • sum 則代表我這個(gè)函數(shù)和調(diào)用我的函數(shù)占用的資源(這里就是 gorourine 數(shù))
  • cum 則代表我這個(gè)函數(shù)和我調(diào)用的函數(shù)占用的資源(這里就是 gouroutine 數(shù))。

上栗子:我現(xiàn)在有個(gè)調(diào)用鏈,funcA -> funcB -> funcC。且在 funcA/B/C 中,除了函數(shù)調(diào)用之外,他們函數(shù)自身也會(huì)各自創(chuàng)建 goroutine,我們假設(shè) funcA/B/C 各自函數(shù)創(chuàng)建的 goroutine 數(shù)為 1/2/3。

那么對于 funcB 而言,它的 flat 指的是 funcB 這個(gè)函數(shù)自己創(chuàng)建的數(shù)量,為 2。而 sum 表示的是 funB 這個(gè)函數(shù),及調(diào)用它的函數(shù) funcA 共同創(chuàng)建的 goroutine 的數(shù)量,為 1+2 =3。而 cum 表示的是 funB 和它調(diào)用的函數(shù) funcC 創(chuàng)建的 goroutine 的數(shù)量,為 2+3 = 5。

而每個(gè)函數(shù)的這三個(gè)指標(biāo)在所有函數(shù)中的占比就為三個(gè)指標(biāo)對應(yīng)的百分比:flat% sum% cum%。

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

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

這是一個(gè)最底層函數(shù),基本所有的 goroutine 創(chuàng)建之后都會(huì)調(diào)用它,所以它自身創(chuàng)建的 goroutine 數(shù)為 flat: 2027,且它沒有下層調(diào)用,所以它的cum 也為 2027。

再看一個(gè)第二列的數(shù)值:

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

bufio.(*Reader).Peek 這個(gè)函數(shù)自身不創(chuàng)建 goroutine(flat = 0),但是調(diào)用它的函數(shù)創(chuàng)建的 goroutine 占比 ( 99.75% ),它的下層調(diào)用創(chuàng)建了 186 個(gè) goroutine,占比 9.15%。

所以我們要分析 goroutine 泄漏的時(shí)候要看哪個(gè)值呢?- 對的,cum。

找到 cum 占比最大的函數(shù),這里一般我們都會(huì)查找非系統(tǒng)調(diào)用函數(shù),(因?yàn)橄到y(tǒng)調(diào)用函數(shù)不大可能有問題),那么我們就知道我們的哪個(gè)應(yīng)用函數(shù)存在 goroutine 泄漏。

這里另外說下,有的文章說可以下一步用 list + 函數(shù)名 的方式來列出最占用資源的行數(shù)。行,當(dāng)然行,但是它的使用情景更為苛刻了 - 需要在現(xiàn)網(wǎng)機(jī)器安裝源碼+依賴庫。而這個(gè)在現(xiàn)網(wǎng)環(huán)境基本是不可能的,所以這個(gè)list 命令在實(shí)際分析中我自己是很少用到的。

cpu問題

如果我們是一個(gè) cpu 暴漲的問題,我們就需要使用pprof 套件中的 profile 工具來定位出哪個(gè)函數(shù)占用 cpu 比較多。

這里我們需要明確一下:

我們說是要定位一個(gè)程序的 cpu 消耗問題,但是這里其實(shí)隱藏了一個(gè)時(shí)間段概念,即:“我們要定位什么時(shí)間段內(nèi)的 cpu 消耗”? - 這里當(dāng)然無法定位過去時(shí)間的 cpu 消耗,我們只能定位從運(yùn)行時(shí)間開始一段時(shí)間內(nèi)的 cpu 消耗,即“從現(xiàn)在開始的 30s 作為取樣樣本,來分析這段時(shí)間內(nèi)的 cpu 耗時(shí)分布”。所以我們需要給 pprof 端口下命令,來定位抓取 30s 內(nèi)的消耗,同樣,我們使用 curl 命令即可。

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

這里的兩個(gè)參數(shù),seconds - 指定樣本時(shí)間,很好理解。而這里輸出的 profile.log 打開是一種二進(jìn)制亂碼,profile 是沒有 debug 參數(shù)的,它只能通過 go tool pprof 工具打開。如果我們安裝 go 工具,或者下載 profile.log 到有 go 工具的環(huán)境,我們才能閱讀。- 這里就是比較麻煩的一點(diǎn)了。

不過所幸,閱讀的方法同 goroutine 的 top 命令類似,唯一不同的是,這里的資源換成了函數(shù)消耗的 cpu 時(shí)間。

(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 那節(jié),就知道這是一個(gè)套路了,如果沒有看,往上翻。 - 1 看 cum 值,2 過濾出非系統(tǒng)函數(shù)調(diào)用,第一個(gè)占用最大的應(yīng)用函數(shù),就是目標(biāo)函數(shù)了,然后分析代碼,有沒有什么 for 死循環(huán),有沒有什么耗 cpu 的行為,就是它了。

內(nèi)存問題

在沒有 goroutine 泄漏的前提下,內(nèi)存為什么占用這么大呢?我們需要 pprof 套件中的兩個(gè)工具,allocs 和 heap。

為什么有兩個(gè)呢?它們兩個(gè)的差別就是allocs 代表的是某個(gè)函數(shù)歷史創(chuàng)建的所有的內(nèi)存(包括已經(jīng)回收了的),而 heap 代表的是當(dāng)前活躍的內(nèi)存占用對象。所以我們能看到 allocs 中的數(shù)值會(huì)比 heap 大了非常多,一般我們使用 heap 比較多。

他們兩個(gè)命令都有 debug 參數(shù),但是 debug 參數(shù)只能為 1 或者沒有。同 goroutine 命令一樣,沒有 debug 參數(shù)就只能通過 go tool 來分析,會(huì)麻煩些。

我們這里分析一個(gè)下 debug=1 的可讀性文本:

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

這里說一下,allocs 和 heap 兩個(gè)命令使用 debug=1 的參數(shù),顯示的內(nèi)容其實(shí)都是一樣的,可讀性文本里面不僅包含了當(dāng)前活躍的內(nèi)存信息,也包含了歷史創(chuàng)建的內(nèi)存信息。比如下面這個(gè)展示:

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/[email protected]/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] 告訴我們,這個(gè)進(jìn)程目前活躍 169 個(gè)對象,占用字節(jié)數(shù)18537048,歷史有過 35672473 個(gè)對象,占用字節(jié)數(shù) 126318298296。 1048576 表示內(nèi)存采樣的頻率,默認(rèn)采樣頻率是512kb 。這里采樣頻率是兩倍的內(nèi)存采樣頻率(1024 x 1024)。

第二行 1: 1810432 [1: 1810432] 告訴我們,下面這個(gè)堆;钴S 1 個(gè)對象,占用字節(jié)數(shù) 1810432,歷史只有過 1 個(gè)對象,占用字節(jié)數(shù) 1810432。

但是實(shí)話說,分析內(nèi)存如果用這個(gè) curl 的方式,會(huì)看的很累,因?yàn)樗呐判虿]有任何太多意義,比如占用最大內(nèi)存的,可能是非常合理的,要看到后面幾名才能知道哪個(gè)占用內(nèi)存大不合理。

還是通過 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 的數(shù)值比 heap 大了不少,相比之下,heap 命令對于分析當(dāng)前服務(wù)內(nèi)存占用更有作用。

其他

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

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

輸出的 html 有如下的有用信息:

204775allocs
0block
0cmdline
1882goroutine
204775heap
0mutex
0profile
18threadcreate
0trace

  • allocs:
    A sampling of all past memory allocations
  • block:
    Stack traces that led to blocking on synchronization primitives
  • cmdline:
    The command line invocation of the current program
  • goroutine:
    Stack traces of all current goroutines
  • heap:
    A sampling of memory allocations of live objects. You can specify the gc GET parameter to run GC before taking the heap sample.
  • mutex:
    Stack traces of holders of contended mutexes
  • profile:
    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.
  • threadcreate:
    Stack traces that led to the creation of new OS threads
  • trace:
    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.
  • 讓 Claude 幫我們解釋下:

    這些是 Go (golang) pprof 包提供的不同的分析工具,每個(gè)工具都有特定的目的,用于分析和調(diào)試 Go 應(yīng)用程序的性能問題。以下是每個(gè)工具的功能:

    1. allocs : 這個(gè)工具提供了過去所有內(nèi)存分配的采樣,可用于識(shí)別內(nèi)存泄漏或優(yōu)化應(yīng)用程序的內(nèi)存使用。
    2. block : 這個(gè)工具提供了阻塞在同步原語(如互斥鎖或通道)上的 goroutine 的堆棧跟蹤,可幫助識(shí)別代碼中潛在的瓶頸或死鎖問題。
    3. cmdline : 這個(gè)工具簡單地返回當(dāng)前程序的命令行調(diào)用,可用于了解程序是如何啟動(dòng)的。
    4. goroutine : 這個(gè)工具提供了所有當(dāng)前 goroutine 的堆棧跟蹤,可幫助了解應(yīng)用程序的整體并發(fā)性,并識(shí)別可能存在問題的 goroutine。
    5. heap : 這個(gè)工具提供了活動(dòng)對象內(nèi)存分配的采樣,可用于識(shí)別內(nèi)存使用模式和潛在的內(nèi)存泄漏。
    6. mutex : 這個(gè)工具提供了持有爭用互斥鎖的 goroutine 的堆棧跟蹤,可幫助識(shí)別應(yīng)用程序中潛在的并發(fā)問題或瓶頸。
    7. profile : 這個(gè)工具生成 CPU 性能分析,可用于識(shí)別應(yīng)用程序中 CPU 密集型的性能問題。您可以使用 seconds GET 參數(shù)指定分析的持續(xù)時(shí)間。
    8. threadcreate : 這個(gè)工具提供了導(dǎo)致新的操作系統(tǒng)線程創(chuàng)建的堆棧跟蹤,可用于了解應(yīng)用程序的整體并發(fā)性和資源利用情況。
    9. trace : 這個(gè)工具生成應(yīng)用程序執(zhí)行的追蹤,可用于分析應(yīng)用程序的整體性能,包括 goroutine 調(diào)度、I/O 和同步事件。您可以使用 seconds GET 參數(shù)指定追蹤的持續(xù)時(shí)間。

    這些工具對于理解和優(yōu)化 Go 應(yīng)用程序的性能非常有價(jià)值,特別是在處理復(fù)雜的并發(fā)系統(tǒng)或內(nèi)存密集型工作負(fù)載時(shí)。

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

    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/

    小編推薦閱讀

    好特網(wǎng)發(fā)布此文僅為傳遞信息,不代表好特網(wǎng)認(rèn)同期限觀點(diǎn)或證實(shí)其描述。

    相關(guān)視頻攻略

    更多

    掃二維碼進(jìn)入好特網(wǎng)手機(jī)版本!

    掃二維碼進(jìn)入好特網(wǎng)微信公眾號(hào)!

    本站所有軟件,都由網(wǎng)友上傳,如有侵犯你的版權(quán),請發(fā)郵件[email protected]

    湘ICP備2022002427號(hào)-10 湘公網(wǎng)安備:43070202000427號(hào)© 2013~2025 haote.com 好特網(wǎng)