中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

go pprof如何使用

發布時間:2023-02-06 11:10:02 來源:億速云 閱讀:190 作者:iii 欄目:編程語言

這篇文章主要介紹“go pprof如何使用”,在日常操作中,相信很多人在go pprof如何使用問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”go pprof如何使用”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

pprof是Go的性能分析工具,在程序運行過程中,可以記錄程序的運行信息,可以是CPU使用情況、內存使用情況、goroutine運行情況等,當需要性能調優或者定位Bug時候,這些記錄的信息是相當重要。使用pprof有多種方式,Go已經現成封裝好了1個“net/http/pprof”,使用簡單的幾行命令,就可以開啟pprof,記錄運行信息,并且提供了Web服務。

go pprof簡介

profile 一般被稱為 性能分析,詞典上的翻譯是 概況(名詞)或者 描述…的概況(動詞)。對于計算機程序來說,它的 profile,就是一個程序在運行時的各種概況信息,包括 cpu 占用情況,內存情況,線程情況,線程阻塞情況等等。知道了程序的這些信息,也就能容易的定位程序中的問題和故障原因 。

pprof是Go的性能分析工具,在程序運行過程中,可以記錄程序的運行信息,可以是CPU使用情況、內存使用情況、goroutine運行情況等,當需要性能調優或者定位Bug時候,這些記錄的信息是相當重要。

golang 對于 profiling 支持的比較好,標準庫就提供了profile庫 “runtime/pprof” 和 “net/http/pprof”,而且也提供了很多好用的可視化工具來輔助開發者做 profiling。

對于在線服務,對于一個 HTTP Server,訪問 pprof 提供的 HTTP 接口,獲得性能數據。當然,實際上這里底層也是調用的 runtime/pprof 提供的函數,封裝成接口對外提供網絡訪問,本文主要介紹"net/http/pprof"的使用。

基本使用

使用pprof有多種方式,Go已經現成封裝好了1個:net/http/pprof,使用簡單的幾行命令,就可以開啟pprof,記錄運行信息,并且提供了Web服務,能夠通過瀏覽器和命令行2種方式獲取運行數據。

web服務中如何開啟監控,來看一個簡單的例子。

package main

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

func main() {
// 開啟pprof,監聽請求
 ip := "0.0.0.0:8080"
 if err := http.ListenAndServe(ip, nil); err != nil {
 	fmt.Printf("start pprof failed on %s\n", ip)
 }
 dosomething()
}

在程序中導入 "net/http/pprof"包,并打開監聽端口,這時候便可以獲取程序的profile,在實際生產中,我們一般將這個功能封裝成一個goroutine。那么開啟之后如何查看呢?有三種方式:

瀏覽器方式

打開一個瀏覽器輸入 ip:port/debug/pprof,回車。

go pprof如何使用

pprof會提供很多性能數據。具體含義為:

  • allocs:內存分配情況的采樣信息

  • blocks:阻塞操作情況的采樣信息 cmdline:程序啟動命令及其參數

  • goroutine:當前所有協程的堆棧信息

  • heap:堆上內存的使用情況的采樣信息 mutex:鎖爭用情況的采樣信息

  • profile:cpu占用情況的采樣信息

  • threadcreate:系統線程創建情況的采樣信息

  • trace:程序運行的跟蹤信息

allocs是所有對象的內存分配,heap是活躍對象的內存分配,后文會有詳細的描述。

1、當 CPU 性能分析啟用后,Go runtime 會每 10ms 就暫停一下,記錄當前運行的 goroutine 的調用堆棧及相關數據。當性能分析數據保存到硬盤后,我們就可以分析代碼中的熱點了。
2、內存性能分析則是在堆(Heap)分配的時候,記錄一下調用堆棧。默認情況下,是每 1000 次分配,取樣一次,這個數值可以改變。棧(Stack)分配 由于會隨時釋放,因此不會被內存分析所記錄。由于內存分析是取樣方式,并且也因為其記錄的是分配內存,而不是使用內存。因此使用內存性能分析工具來準確判斷程序具體的內存使用是比較困難的。
3、阻塞分析是一個很獨特的分析,它有點兒類似于 CPU 性能分析,但是它所記錄的是 goroutine 等待資源所花的時間。阻塞分析對分析程序并發瓶頸非常有幫助,阻塞性能分析可以顯示出什么時候出現了大批的 goroutine 被阻塞了。阻塞性能分析是特殊的分析工具,在排除 CPU 和內存瓶頸前,不應該用它來分析。

當然,如果你點進任何一個鏈接,便會發現,可讀性差,幾乎無法分析。如圖:

go pprof如何使用

點擊heap,拉到最底部,可以看到一些有意思的數據,有時候也有可能會對問題排查有幫助,但一般不用。

heap profile: 3190: 77516056 [54762: 612664248] @ heap/1048576
1: 29081600 [1: 29081600] @ 0x89368e 0x894cd9 0x8a5a9d 0x8a9b7c 0x8af578 0x8b4441 0x8b4c6d 0x8b8504 0x8b2bc3 0x45b1c1
#    0x89368d    github.com/syndtr/goleveldb/leveldb/memdb.(*DB).Put+0x59d
#    0x894cd8    xxxxx/storage/internal/memtable.(*MemTable).Set+0x88
#    0x8a5a9c    xxxxx/storage.(*snapshotter).AppendCommitLog+0x1cc
#    0x8a9b7b    xxxxx/storage.(*store).Update+0x26b
#    0x8af577    xxxxx/config.(*config).Update+0xa7
#    0x8b4440    xxxxx/naming.(*naming).update+0x120
#    0x8b4c6c    xxxxx/naming.(*naming).instanceTimeout+0x27c
#    0x8b8503    xxxxx/naming.(*naming).(xxxxx/naming.instanceTimeout)-fm+0x63

......

# runtime.MemStats
# Alloc = 2463648064
# TotalAlloc = 31707239480
# Sys = 4831318840
# Lookups = 2690464
# Mallocs = 274619648
# Frees = 262711312
# HeapAlloc = 2463648064
# HeapSys = 3877830656
# HeapIdle = 854990848
# HeapInuse = 3022839808
# HeapReleased = 0
# HeapObjects = 11908336
# Stack = 655949824 / 655949824
# MSpan = 63329432 / 72040448
# MCache = 38400 / 49152
# BuckHashSys = 1706593
# GCSys = 170819584
# OtherSys = 52922583
# NextGC = 3570699312
# PauseNs = [1052815 217503 208124 233034 ......]
# NumGC = 31
# DebugGC = false

  • Sys: 進程從系統獲得的內存空間,虛擬地址空間

  • HeapAlloc:進程堆內存分配使用的空間,通常是用戶new出來的堆對象,包含未被gc掉的。

  • HeapSys:進程從系統獲得的堆內存,因為golang底層使用TCmalloc機制,會緩存一部分堆內存,虛擬地址空間

  • PauseNs:記錄每次gc暫停的時間(納秒),最多記錄256個最新記錄。

  • NumGC: 記錄gc發生的次數

命令行方式

除了瀏覽器,Go還提供了命令行的方式,能夠獲取以上信息,這種方式用起來更方便。

使用命令go tool pprof url可以獲取指定的profile文件,此命令會發起http請求,然后下載數據到本地,之后進入交互式模式,就像gdb一樣,可以使用命令查看運行信息,以下為使用示例:

# 下載cpu profile,默認從當前開始收集30s的cpu使用情況,需要等待30s
go tool pprof http://localhost:8080/debug/pprof/profile # 30-second CPU profile
go tool pprof http://localhost:8080/debug/pprof/profile?seconds=120 # wait 120s

# 下載heap profile
go tool pprof http://localhost:8080/debug/pprof/heap # heap profile

# 下載goroutine profile
go tool pprof http://localhost:8080/debug/pprof/goroutine # goroutine profile

# 下載block profile
go tool pprof http://localhost:8080/debug/pprof/block # goroutine blocking profile

# 下載mutex profile
go tool pprof http://localhost:8080/debug/pprof/mutex

接下來用一個例子來說明最常用的四個命令:web、top、list、traces

接下來以內存分析舉例,cpu和goroutine等分析同理,讀者可以自行舉一反三。

首先,我們通過命令go tool pprof url獲取指定的profile/heap文件,隨后自動進入命令行。如圖:

go pprof如何使用

第一步,我們首先輸入web命令,這時瀏覽器會彈出各個函數之間的調用圖,以及內存的之間的關系。如圖:

go pprof如何使用

這個圖的具體讀法,可參照:中文文檔 或者英文文檔 這里不多贅述。只需要了解越紅越大的方塊,有問題的可能性就越大,代表可能占用了更多的內存,如果在cpu的圖中代表消耗了更多cpu資源,以此類推。
接下來 top、list、traces三步走可以看出很多想要的結果。

top 按指標大小列出前10個函數,比如內存是按內存占用多少,CPU是按執行時間多少。
top會列出5個統計數據:

  • flat: 本函數占用的內存量。

  • flat%: 本函數內存占使用中內存總量的百分比。

  • sum%: 之前函數flat的累計和。

  • cum:是累計量,假如main函數調用了函數f,函數f占用的內存量,也會記進來。

  • cum%: 是累計量占總量的百分比。

go pprof如何使用

這樣我們可以看到到底是具體哪些函數占用了多少內存。

當然top后也可以接參數,top n可以列出前n個函數。

list可以查看某個函數的代碼,以及該函數每行代碼的指標信息,如果函數名不明確,會進行模糊匹配,比如list main會列出main.mainruntime.main。現在list sendToASR試一下。

go pprof如何使用

可以看到切片中增加元素時,占用了很多內存,左右2個數據分別是flatcum

traces 打印所有調用棧,以及調用棧的指標信息。使用方式為traces+函數名(模糊匹配)。

go pprof如何使用

在命令行之中,還有一個重要的參數 -base,假設我們已經通過命令行得到profile1與profile2,使用go tool pprof -base profile1 profile2,便可以以profile1為基礎,得出profile2在profile1之上出現了哪些變化。通過兩個時間切片的比較,我們可以清晰的了解到,兩個時間節點之中發生的變化,方便我們定位問題(很重要!!!!)

可視化界面

打開可視化界面的方式為:go tool pprof -http=:1234 http://localhost:8080/debug/pprof/heap 其中1234是我們指定的端口

go pprof如何使用

Top

go pprof如何使用

該視圖與前面所講解的 top 子命令的作用和含義是一樣的,因此不再贅述。

Graph

為函數調用圖,不在贅述.

Peek

go pprof如何使用

此視圖相較于 Top 視圖,增加了所屬的上下文信息的展示,也就是函數的輸出調用者/被調用者。

Source

go pprof如何使用

該視圖主要是增加了面向源代碼的追蹤和分析,可以看到其開銷主要消耗在哪里。

Flame Graph

go pprof如何使用

對應資源消耗的火焰圖,火焰圖的讀法,這里不贅述,不是本文的重點。

第二個下拉菜單如圖所示:

go pprof如何使用

alloc_objects,alloc_space表示應用程序分配過的資源,不管有沒有釋放,inuse_objects,inuse_space表示應用程序的還未釋放的資源常配情況。

NameMeaning
inuse_spaceamount of memory allocated and not released yet
inuse_objectsamount of objects allocated and not released yet
alloc_spacetotal amount of memory allocated (regardless of released)
alloc_objectstotal amount of objects allocated (regardless of released)

第一個下拉菜單可以與第二個下拉菜單相組合,可以查看臨時變量的火焰圖,常駐變量的內存調用圖等。

tips

  • 程序運行占用較大的內存,可以通過 inuse_space 來體現.

  • 存在非常頻繁的 GC 活動,通常意味著 alloc_space非常高,而程序運行過程中并沒有消耗太多的內存(體現為 inuse_space 并不高),當然也可能出現 GC 來不及回收,因此c出現inuse_space 也變高的情況。這種情況下同樣會大量消耗CPU。

  • 內存泄漏,通常 alloc_space 較高,且
    inuse_space 也較高。

操作方法

上面我們已經看完了go pprof 的所有操作,接下來講解一下go tool pprof 的具體使用流程。

  • 通過監控平臺監測到內存或cpu問題。

  • 通過瀏覽器方式大致判斷是哪些可能的問題。

  • 通過命令行方式抓取幾個時間點的profile

  • 使用web命令查看函數調用圖

  • 使用top 、traces、list 命令定位問題

  • 如果出現了goroutine泄漏或者內存泄漏等隨著時間持續增長的問題,go tool pprof -base比較兩個不同時間點的狀態更方便我們定位問題。

具體案例

案例一:goroutine泄漏

啟動程序后,用瀏覽器方式打開profile:

go pprof如何使用

發現內存持續上升,同時goroutine也在持續上升,初步判斷,內存泄漏是由于goroutine泄漏導致的。

接下來通過命令行方式抓取goroutine的情況:命令行輸入:go tool pprof localhost:8080/debug/pprof/goroutine,獲取結果。

分析的流程

一、使用web命令查看調用圖,大概了解目前的goroutine的泄露情況:

go pprof如何使用

通過觀察,最引入注目的便是runtime.gopark這個函數,這個函數在所有goroutine泄漏時都會出現,并且是大頭,接下來我們研究一下這個函數的作用:

func gopark(unlockf func(*g, unsafe.Pointer) bool, lock unsafe.Pointer, reason waitReason, traceEv byte, traceskip int) {
 mp := acquirem()
 gp := mp.curg
 status := readgstatus(gp)
 mp.waitlock = lock
 mp.waitunlockf = unlockf
 gp.waitreason = reason
 mp.waittraceev = traceEv
 mp.waittraceskip = traceskip
 releasem(mp)
 
 mcall(park_m)
}

該函數的作用為:

1、調用acquirem函數:獲取當前goroutine所綁定的m,設置各類所需參數。調用 releasem 函數將當前 goroutine 和其 m 的綁定關系解除。

2、調用 park_m 函數:將當前 goroutine 的狀態從 _Grunning 切換為 _Gwaiting,也就是等待狀態。刪除 m 和當前 goroutine m→curg(簡稱gp)之間的關聯。

3、調用 mcall 函數,僅會在需要進行 goroutiine 切換時會被調用:切換當前線程的堆棧,從 g 的堆棧切換到 g0 的堆棧并調用 fn(g) 函數。將 g 的當前 PC/SP 保存在 g->sched 中,以便后續調用 goready 函數時可以恢復運行現場。

綜上:該函數的關鍵作用就是將當前的 goroutine 放入等待狀態,這意味著 goroutine 被暫時被擱置了,也就是被運行時調度器暫停了。
所以出現goroutine泄漏一定會調用這個函數,這個函數不是goroutine泄漏的原因,而是goroutine泄漏的結果。

此外,我們發現有兩個函數的goroutine的達到了67,很可疑,在我們接下來的驗證中要格外留意。

二、使用top命令,獲取更加具體的函數信息:

go pprof如何使用

與上面分析的結論相似,我們要將關注點放在三個開啟了67個goroutine的函數。

三、traces+函數名,查看調用棧,這一步在函數調用很復雜,無法從調用圖里面清晰的看出時使用,幫助我們更清晰的了解函數的調用堆棧:

go pprof如何使用

四、使用list+函數名,查看具體代碼的問題。

go pprof如何使用

go pprof如何使用

通過list命令我們可以清楚的看出問題代碼是堵塞在哪里。

goroutine泄漏知識

什么是goroutine泄漏:如果你啟動了一個 goroutine,但并沒有符合預期的退出,直到程序結束,此goroutine才退出,這種情況就是 goroutine 泄露。當 goroutine 泄露發生時,該 goroutine 的棧(一般 2k 內存空間起)一直被占用不能釋放,goroutine 里的函數在堆上申請的空間也不能被 垃圾回收器 回收。這樣,在程序運行期間,內存占用持續升高,可用內存越來也少,最終將導致系統崩潰。
什么時候出現goroutine泄漏:goroutine泄露一般是因為channel操作阻塞而導致整個routine一直阻塞等待或者 goroutine 里有死循環。
具體細分一下:

  • 從 channel 里讀,但是沒有寫。

  • 向 unbuffered channel 寫,但是沒有讀。

  • 向已滿的 buffered channel 寫,但是沒有讀。

  • select操作在所有case上阻塞。

  • goroutine進入死循環中,導致資源一直無法釋放。

select底層也是channel實現的,如果所有case上的操作阻塞,select內部的channel便會阻塞,goroutine也無法繼續執行。所以我們使用channel時一定要格外小心。

通過分析上面兩幅圖的情況,可以判斷是因為select在所有case上死鎖了,再深入代碼分析,是因為項目中的的語音模型并發能力弱,在語音發包速度快起來的時候無法處理,導致select不滿足條件,導致goroutine泄漏,應該在for循環之外加一個asr←nil,保證func2的select一定會滿足,同時提高模型的并發能力,使得func1的不會阻塞。
防止goroutine泄漏的建議:

  • 創建goroutine時就要想好該goroutine該如何結束。

  • 使用channel時,要考慮到 channel阻塞時協程可能的行為,是否會創建大量的goroutine。

  • goroutine中不可以存在死循環。

案例二:內存泄漏

我們通過grafana發現內存出現泄漏:

go pprof如何使用

這一次我們不使用命令行,而是使用圖形化界面來定位問題。
輸入 go tool pprof -http=:1234 localhost:8080/debug/pprof/heap:

go pprof如何使用發現內存占用很有可能是byte.makeSlice()導致的,火焰圖看的更加清晰:

go pprof如何使用

而調用byte.makeSlice()的函數為標準庫中的ioutil.ReadAll(),接下來我們只需要研究這個標準庫函數的實現即可。

func readAll(r io.Reader, capacity int64) (b []byte, err error) {
	buf := bytes.NewBuffer(make([]byte, 0, capacity))
	defer func() {
		e := recover()
		if e == nil {
			return
		}
		if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
			err = panicErr
		} else {
			panic(e)
		}
	}()
	_, err = buf.ReadFrom(r)
	return buf.Bytes(), err
}

// bytes.MinRead = 512
func ReadAll(r io.Reader) ([]byte, error) {
	return readAll(r, bytes.MinRead)
}

可以看到 ioutil.ReadAll 每次都會分配初始化一個大小為 bytes.MinRead 的 buffer ,bytes.MinRead 在 Golang 里是一個常量,值為 512 。就是說每次調用 ioutil.ReadAll 都先會分配一塊大小為 512 字節的內存。
接下來看一下ReadFrom函數的實現:

func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error) {
	b.lastRead = opInvalid
	// If buffer is empty, reset to recover space.
	if b.off >= len(b.buf) {
		b.Truncate(0)
	}
	for {
		if free := cap(b.buf) - len(b.buf); free < MinRead {
			// not enough space at end
			newBuf := b.buf
			if b.off+free < MinRead {
				// not enough space using beginning of buffer;
				// double buffer capacity
				newBuf = makeSlice(2*cap(b.buf) + MinRead)
			}
			copy(newBuf, b.buf[b.off:])
			b.buf = newBuf[:len(b.buf)-b.off]
			b.off = 0
		}
		m, e := r.Read(b.buf[len(b.buf):cap(b.buf)])
		b.buf = b.buf[0 : len(b.buf)+m]
		n += int64(m)
		if e == io.EOF {
			break
		}
		if e != nil {
			return n, e
		}
	}
	return n, nil // err is EOF, so return nil explicitly
}

ReadFrom函數主要作用就是從 io.Reader 里讀取的數據放入 buffer 中,如果 buffer 空間不夠,就按照每次 2x + MinRead 的算法遞增,這里 MinRead 的大小也是 512 Bytes 。
項目讀取的音頻文件一般很大,buffer不夠用,會一直調用makeSlice擴容,消耗大量內存,但是僅僅這樣,只是程序執行時消耗了比較多的內存,并未有內存泄露的情況,那服務器又是如何內存不足的呢?這就不得不扯到 Golang 的 GC 機制。

GC算法的觸發時機

golang的GC算法為三色算法,按理說會回收臨時變量,但是觸發GC的時機導致了這個問題:

  • 已分配的 Heap 到達某個閾值,會觸發 GC, 該閾值由上一次 GC 時的 HeapAlloc 和 GCPercent 共同決定

  • 每 2 分鐘會觸發一次強制的 GC,將未 mark 的對象釋放,但并不還給 OS

  • 每 5 分鐘會掃描一個 Heap, 對于一直沒有被訪問的 Heap,歸還給 OS

ioutil.ReadAll會將全部的數據加載到內存,一個大請求會多次調用makeSlice 分配很多內存空間,并發的時候,會在很短時間內占用大量的系統內存,然后將 GC 閾值增加到一個很高的值,這個時候要 GC 就只有等 2 分鐘一次的強制 GC。這樣內存中的數據無法及時GC,同時閾值還在不停的升高,導致GC的效率越來越低,最終導致緩慢的內存泄漏。

解決方法

//req.AduioPack,err=ioutil.ReadAll(c.Resquest.Body)

buffer:=bytes.NewBuffer(make[]byte,0,6400)
_,err:=io.Copy(buffer,c.Resquest.Body)
temp:=buffer.Bytes()
req.AduioPack=temp

不是一次性把文件讀入內存,而是申請固定的內存大小。

到此,關于“go pprof如何使用”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

阳东县| 拜泉县| 新民市| 汉中市| 察哈| 那曲县| 长寿区| 扶风县| 万盛区| 平远县| 长葛市| 贺兰县| 泾阳县| 无锡市| 阿拉尔市| 南部县| 锡林浩特市| 桦甸市| 霸州市| 德庆县| 阿拉尔市| 仙居县| 东莞市| 白玉县| 定西市| 江华| 阿克苏市| 富裕县| 鄂尔多斯市| 永吉县| 德清县| 灌云县| 库伦旗| 亚东县| 舞阳县| 古田县| 城市| 湖北省| 古交市| 黄陵县| 凤山县|