欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

golang?pprof?監(jiān)控goroutine?thread統(tǒng)計(jì)原理詳解

 更新時(shí)間:2023年04月07日 15:14:14   作者:藍(lán)胖子的編程夢  
這篇文章主要為大家介紹了golang?pprof?監(jiān)控goroutine?thread統(tǒng)計(jì)原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

在之前 golang pprof監(jiān)控 系列文章里我分別介紹了go trace以及go pprof工具對memory,block,mutex這些維度的統(tǒng)計(jì)原理,今天我們接著來介紹golang pprof工具對于goroutine 和thread的統(tǒng)計(jì)原理。

還記得在golang pprof監(jiān)控系列 memory,block,mutex 使用 文章里,通過http接口的方式暴露的方式展現(xiàn) 指標(biāo)信息那個(gè)網(wǎng)頁圖嗎?

這一節(jié),我將會(huì)介紹其中的goroutine部分和threadcreate部分。

老規(guī)矩,在介紹統(tǒng)計(jì)原理前,先來看看http接口暴露的方式暴露了哪些信息。

http 接口暴露的方式

讓我們點(diǎn)擊網(wǎng)頁的goroutine 鏈接。。。

goroutine profile 輸出信息介紹

進(jìn)入到了一個(gè)這樣的界面,我們挨個(gè)分析下網(wǎng)頁展現(xiàn)出來的信息:

首先地址欄 /debug/pprof/goroutine?debug= 1 代表這是在訪問goroutine指標(biāo)信息,debug =1 代表訪問的內(nèi)容將會(huì)以文本可讀的形式展現(xiàn)出來。 debug=0 則是會(huì)下載一個(gè)goroutine指標(biāo)信息的二進(jìn)制文件,這個(gè)文件可以通過go tool pprof 工具去進(jìn)行分析,關(guān)于go tool pprof 的使用網(wǎng)上也有相當(dāng)多的資料,這里就不展開了。 debug = 2 將會(huì)把當(dāng)前所有協(xié)程的堆棧信息以文本可讀形式展示在網(wǎng)頁上。如下圖所示:

debug =2 時(shí)的 如上圖所示,41代表協(xié)程的id,方括號內(nèi)running代表了協(xié)程的狀態(tài)是運(yùn)行中,接著就是該協(xié)程此時(shí)的堆棧信息了。

讓我們再回到debug = 1的分析上面去,剛才分析完了地址欄里的debug參數(shù),接著,我們看輸出的第一行

goroutine profile: total 6
1 @ 0x102ad6c60 0x102acf7f4 0x102b04de0 0x102b6e850 0x102b6e8dc 0x102b6f79c 0x102c27d04 0x102c377c8 0x102d0fc74 0x102bea72c 0x102bebec0 0x102bebf4c 0x102ca4af0 0x102ca49dc 0x102d0b084 0x102d10f30 0x102d176a4 0x102b09fc4
#	0x102b04ddf	internal/poll.runtime_pollWait+0x5f		/Users/xiongchuanhong/goproject/src/go/src/runtime/netpoll.go:303
#	0x102b6e84f	internal/poll.(*pollDesc).wait+0x8f		/Users/xiongchuanhong/goproject/src/go/src/internal/poll/fd_poll_runtime.go:84

......

goroutine profile 表明了這個(gè)profile的類型。

total 6 代表此時(shí)一共有6個(gè)協(xié)程。

接著是下面一行,1 代表了在這個(gè)堆棧上,只有一個(gè)協(xié)程在執(zhí)行。但其實(shí)在計(jì)算出數(shù)字1時(shí),并不僅僅按堆棧去做區(qū)分,還依據(jù)了協(xié)程labels值,也就是 協(xié)程的堆棧和lebels標(biāo)簽值 共同構(gòu)成了一個(gè)key,而數(shù)字1就是在遍歷所有協(xié)程信息時(shí),對相同key進(jìn)行累加計(jì)數(shù)得來的。

我們可以通過下面的方式為協(xié)程設(shè)置labels。

	pprof.SetGoroutineLabels(pprof.WithLabels(context.Background(), pprof.Labels("name", "lanpangzi", "age", "18")))

通過上述代碼,我可以為當(dāng)前協(xié)程設(shè)置了兩個(gè)標(biāo)簽值,分別是name和age,設(shè)置label值之后,再來看debug=1后的網(wǎng)頁輸出,可以發(fā)現(xiàn) 設(shè)置的labels出現(xiàn)了。

1 @ 0x104f86c60 0x104fb7358 0x105236368 0x104f867ec 0x104fba024
# labels: {"age":"18", "name":"lanpangzi"}
#	0x104fb7357	time.Sleep+0x137	/Users/xiongchuanhong/goproject/src/go/src/runtime/time.go:193
#	0x105236367	main.main+0x437		/Users/xiongchuanhong/goproject/src/go/main/main.go:46
#	0x104f867eb	runtime.main+0x25b	/Users/xiongchuanhong/goproject/src/go/src/runtime/proc.go:255

而數(shù)字1之后,就是協(xié)程正在執(zhí)行的堆棧信息了。至此,goroutine指標(biāo)的輸出信息介紹完畢。

threadcreate 輸出信息介紹

介紹完goroutine指標(biāo)的輸出信息后,再來看看threadcreate 線程創(chuàng)建指標(biāo)的 輸出信息。

老規(guī)矩,先看地址欄,debug=1代表 輸出的是文本可讀的信息,threadcreate 就沒有debug=2的特別輸出了,debug=0時(shí) 同樣也會(huì)下載一個(gè)可供go tool pprof分析的二進(jìn)制文件。

接著threadcreate pfofile表明了profile的類型, total 12 代表了此時(shí)總共有12個(gè)線程被創(chuàng)建,然后緊接著是11 代表了在這個(gè)總共有11個(gè)線程是在這個(gè)堆棧的代碼段上被創(chuàng)建的,注意這里后面沒有堆棧內(nèi)容,說明runtime在創(chuàng)建線程時(shí),并沒有把此時(shí)的堆棧記錄下來,原因有可能是 這個(gè)線程是runtime自己使用的,堆棧沒有必要展示給用戶,所以干脆不記錄了,具體原因這里就不深入研究了。

下面輸出的內(nèi)容可以看到在main方法里面創(chuàng)建了一個(gè)線程,runtime.newm 方法內(nèi)部,runtime會(huì)啟動(dòng)一個(gè)系統(tǒng)線程。

threadcreate 輸出內(nèi)容比較簡單,沒有過多可以講的。

程序代碼暴露指標(biāo)信息

看完了http接口暴露著兩類指標(biāo)的方式,我們再來看看如何通過代碼來暴露他們。 還記得在golang pprof監(jiān)控系列memory,block,mutex 使用 是如何通過程序代碼 暴露memory block mutex 指標(biāo)的嗎,goroutine 和 threadcreate 和他們一樣,也是通過pprof.Lookup方法進(jìn)行暴露的。

os.Remove("goroutine.out")
	f, _ := os.Create("goroutine.out")
	defer f.Close()
	err := pprof.Lookup("goroutine").WriteTo(f, 1)
	if err != nil {
		log.Fatal(err)
	}
	
	.... 
	
	os.Remove("threadcreate.out")
	f, _ := os.Create("threadcreate.out")
	defer f.Close()
	err := pprof.Lookup("threadcreate").WriteTo(f, 1)
	if err != nil {
		log.Fatal(err)
	}

無非就是將pprof.Lookup的傳入的參數(shù)值改成對應(yīng)的指標(biāo)名即可。

接著我們來看看runtime內(nèi)部是如何對這兩種類型的指標(biāo)進(jìn)行統(tǒng)計(jì)的,好的,正戲開始。

統(tǒng)計(jì)原理介紹

無論是 goroutine 還是threadcreate 的指標(biāo)信息的輸出,都是調(diào)用了同一個(gè)方法writeRuntimeProfile。 golang 源碼版本 go1.17.12。

// src/runtime/pprof/pprof.go:708
func writeRuntimeProfile(w io.Writer, debug int, name string, fetch func([]runtime.StackRecord, []unsafe.Pointer) (int, bool)) error {
	var p []runtime.StackRecord
	var labels []unsafe.Pointer
	n, ok := fetch(nil, nil)
	for {
		p = make([]runtime.StackRecord, n+10)
		labels = make([]unsafe.Pointer, n+10)
		n, ok = fetch(p, labels)
		if ok {
			p = p[0:n]
			break
		}
	}
	return printCountProfile(w, debug, name, &runtimeProfile{p, labels})
}

讓我們來分析下這個(gè)函數(shù),函數(shù)會(huì)傳遞一個(gè)fetch 方法,goroutine和threadcreate信息在輸出時(shí)選擇了不同的fetch方法來獲取到各自的信息。

為了對主干代碼有比較清晰的認(rèn)識,先暫時(shí)不看fetch方法的具體實(shí)現(xiàn),此時(shí)我們只需要知道,fetch方法可以將需要的指標(biāo)信息 獲取到,并且將信息的堆棧存到變量名為p的堆棧類型的切片里,然后將labels信息,存儲到 變量名為labels的切片里。

注意: 只有g(shù)oroutine類型的指標(biāo)才有l(wèi)abels信息

獲取到了堆棧信息,labels 信息,接著就是要將這些信息進(jìn)行輸出了,進(jìn)行輸出的函數(shù)是 上述源碼里的最后一行 中 的printCountProfile 函數(shù)。

printCountProfile 函數(shù)的邏輯比較簡單,我簡單概括下,輸出的時(shí)候會(huì)將 printCountProfile 參數(shù)中的堆棧信息連同labels構(gòu)成的結(jié)構(gòu)體 進(jìn)行遍歷, 堆棧信息和labels信息組合作為key,對相同key的內(nèi)容進(jìn)行累加計(jì)數(shù)。最后 printCountProfile 將根據(jù)debug的值的不同選擇不同的輸出方式,例如debug=0是二進(jìn)制文件下載 方式 ,debug=1則是 網(wǎng)頁文本可讀方式進(jìn)行輸出

至此,對goroutine和threadcreate 指標(biāo)信息的輸出過程應(yīng)該有了解了,即通過fetch方法獲取到指標(biāo)信息,然后通過printCountProfile 方法對指標(biāo)信息進(jìn)行輸出。

fetch 方法的具體實(shí)現(xiàn),我們還沒有開始介紹,現(xiàn)在來看看,goroutine和threadcreate信息在輸出時(shí)選擇了不同的fetch方法來獲取到各自的信息。

源碼如下:

// src/runtime/pprof/pprof.go:661  
func writeThreadCreate(w io.Writer, debug int) error {
	return writeRuntimeProfile(w, debug, "threadcreate", func(p []runtime.StackRecord, _ []unsafe.Pointer) (n int, ok bool) {
		return runtime.ThreadCreateProfile(p)
	})
}

// src/runtime/pprof/pprof.go:680 
func writeGoroutine(w io.Writer, debug int) error {
	if debug >= 2 {
		return writeGoroutineStacks(w)
	}
	return writeRuntimeProfile(w, debug, "goroutine", runtime_goroutineProfileWithLabels)
}

goroutine 指標(biāo)信息在輸出時(shí),會(huì)選擇runtime_goroutineProfileWithLabels函數(shù)來獲取goroutine指標(biāo),而threadcreate 則會(huì)調(diào)用 runtime.ThreadCreateProfile(p) 去獲取threadcreate指標(biāo)信息。

goroutine fetch 函數(shù)實(shí)現(xiàn)

runtime_goroutineProfileWithLabels 方法的實(shí)現(xiàn)是由go:linkname 標(biāo)簽鏈接過去的,實(shí)際底層實(shí)現(xiàn)的方法是 runtime_goroutineProfileWithLabels。

// src/runtime/mprof.go:744
//go:linkname runtime_goroutineProfileWithLabels runtime/pprof.runtime_goroutineProfileWithLabels
func runtime_goroutineProfileWithLabels(p []StackRecord, labels []unsafe.Pointer) (n int, ok bool) {
	return goroutineProfileWithLabels(p, labels)
}

goroutineProfileWithLabels 就是實(shí)際獲取goroutine堆棧和標(biāo)簽的方法了。

我們往goroutineProfileWithLabels 傳遞了兩個(gè)數(shù)組,分別用于存儲堆棧信息,和labels信息,而goroutineProfileWithLabels 則負(fù)責(zé)將兩個(gè)數(shù)組填充上對應(yīng)的信息。

goroutineProfileWithLabels 的邏輯也比較容易,我這里僅僅簡單概括下,其內(nèi)部會(huì)通過一個(gè)全局變量allgptr 去遍歷所有的協(xié)程,allgptr 保存了程序中所有的協(xié)程的地址, 而協(xié)程的結(jié)構(gòu)體g內(nèi)部,有一個(gè)叫做label的屬性,這個(gè)值就代表協(xié)程的標(biāo)簽值,在遍歷協(xié)程時(shí),通過該屬性便可以獲取到標(biāo)簽值了。

threadcreate fetch 函數(shù)實(shí)現(xiàn)

runtime.ThreadCreateProfile 是 獲取threadcreate 指標(biāo)的方法。

源碼如下:

func ThreadCreateProfile(p []StackRecord) (n int, ok bool) {
	first := (*m)(atomic.Loadp(unsafe.Pointer(&allm)))
	for mp := first; mp != nil; mp = mp.alllink {
		n++
	}
	if n <= len(p) {
		ok = true
		i := 0
		for mp := first; mp != nil; mp = mp.alllink {
			p[i].Stack0 = mp.createstack
			i++
		}
	}
	return
}

首先是獲取到allm變量的地址,allm是一個(gè)全局變量,它其實(shí)是 存儲所有m鏈表 的表頭元素。

// src/runtime/runtime2.go:1092
var (
	allm       *m
	.....

在golang里,每創(chuàng)建一個(gè)m結(jié)構(gòu)便會(huì)在底層創(chuàng)建一個(gè)系統(tǒng)線程,所以你可以簡單的認(rèn)為m就是代表了一個(gè)線程??梢灾笊钊肓私庀耮pm模型。

for mp := first; mp != nil; mp = mp.alllink {
			p[i].Stack0 = mp.createstack
			i++
		}

然后 ThreadCreateProfile 里 這段邏輯就是遍歷了整個(gè)m鏈表,將m結(jié)構(gòu)體保存的堆棧信息賦值給 參數(shù)p,p則是我們需要填充的堆棧信息數(shù)組,在m結(jié)構(gòu)體里,alllink是一個(gè)指向鏈表下一個(gè)元素的指針,每次新創(chuàng)建m時(shí),會(huì)將新m插入到表頭位置,然后更新allm變量。

總結(jié)

至此,goroutine 和threadcreate的使用和原理都介紹完了,他們比起之前的memory,block之類的統(tǒng)計(jì)相對來說比較簡單,簡而言之就是遍歷一個(gè)全局變量allgptr或者allm ,遍歷時(shí)獲取到協(xié)程或者線程的堆棧信息和labels信息,然后將這些信息進(jìn)行輸出即可。

以上就是golang pprof 監(jiān)控goroutine thread統(tǒng)計(jì)原理詳解的詳細(xì)內(nèi)容,更多關(guān)于go pprof goroutine thread統(tǒng)計(jì)的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論