最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Golang?pprof監(jiān)控之cpu占用率統(tǒng)計原理詳解

 更新時間:2023年04月10日 11:40:47   作者:藍胖子的編程夢  
經過前面的幾節(jié)對pprof的介紹,對pprof統(tǒng)計的原理算是掌握了七八十了,但唯獨還沒有分析pprof?工具是如何統(tǒng)計cpu使用情況的,今天我們來分析下這部分

經過前面的幾節(jié)對pprof的介紹,對pprof統(tǒng)計的原理算是掌握了七八十了,我們對memory,block,mutex,trace,goroutine,threadcreate這些維度的統(tǒng)計原理都進行了分析,但唯獨還沒有分析pprof 工具是如何統(tǒng)計cpu使用情況的,今天我們來分析下這部分。

http 接口暴露的方式

還記得 golang pprof監(jiān)控系列(2) —— memory,block,mutex 使用 里我們啟動了一個http服務來暴露各種性能指標信息。讓我們再回到當時啟動http服務看到的網頁圖。

當點擊上圖中profile鏈接時,便會下載一個關于cpu指標信息的二進制文件。這個二進制文件同樣可以用go tool pprof 工具去分析,同樣,關于go tool pprof的使用不是本文的重點,網上的資料也相當多,所以我略去了這部分。

緊接著,我們來快速看下如何用程序代碼的方式生成cpu的profile文件。

程序代碼生成profile

os.Remove("cpu.out")
	f, _ := os.Create("cpu.out")
	defer f.Close()
	pprof.StartCPUProfile(f)
	defer 	pprof.StopCPUProfile()
	// .... do other things

代碼比較簡單,pprof.StartCPUProfile 則開始統(tǒng)計 cpu使用情況,pprof.StopCPUProfile則停止統(tǒng)計cpu使用情況,將程序使用cpu的情況寫入cpu.out文件。cpu.out文件我們則可以用go tool pprof去分析了。

好的,在快速的看完如何在程序中暴露cpu性能指標后,我們來看看golang是如何統(tǒng)計各個函數(shù)cpu使用情況的。接下來,正戲開始。

cpu 統(tǒng)計原理分析

首先要明白,我們究竟要統(tǒng)計的是什么內容?我們需要知道cpu的使用情況,換言之就是cpu的工作時間花在了哪些函數(shù)上,最后是不是就是看函數(shù)在cpu上的工作時長。

那么函數(shù)的在cpu上工作時長應該如何去進行統(tǒng)計?

golang還是采用部分采樣的方式,通過settimmer 系統(tǒng)調用設置了 發(fā)送SIGPROF 的定時器,當達到runtime.SetCPUProfileRate設置的周期間隔時,操作系統(tǒng)就會向進程發(fā)送SIGPROF 信號,默認情況下是100Mz(10毫秒)。

一旦設置了 發(fā)送SIGPROF信號的定時器,操作系統(tǒng)便會定期向進程發(fā)送SIGPROF信號。

設置定時器的代碼便是在我們調用pprof.StartCPUProfile方法開啟cpu信息采樣的時候。代碼如下,

// src/runtime/pprof/pprof.go:760
func StartCPUProfile(w io.Writer) error {
	const hz = 100
	cpu.Lock()
	defer cpu.Unlock()
	if cpu.done == nil {
		cpu.done = make(chan bool)
	}
	// Double-check.
	if cpu.profiling {
		return fmt.Errorf("cpu profiling already in use")
	}
	cpu.profiling = true
	runtime.SetCPUProfileRate(hz)
	go profileWriter(w)
	return nil
}

在倒數(shù)第三行的時候調用了設置采樣的周期,并且緊接著profileWriter 就是用一個協(xié)程啟動后去不斷的讀取cpu的采樣數(shù)據(jù)寫到文件里。而調用settimer的地方就是在runtime.SetCPUProfileRate里,runtime.SetCPUProfileRate最終會調用 setcpuprofilerate方法 ,setcpuprofilerate 又會去調用setProcessCPUProfiler方法設置settimer 定時器。

// src/runtime/signal_unix.go:269
func setProcessCPUProfiler(hz int32) {
  .....
		var it itimerval
		it.it_interval.tv_sec = 0
		it.it_interval.set_usec(1000000 / hz)
		it.it_value = it.it_interval
		setitimer(_ITIMER_PROF, &it, nil)
....	

經過上述步驟后,cpu的采樣就真正開始了,之后就是定時器被觸發(fā)送SIGPROF信號,進程接收到這個信號后,會對當前函數(shù)的調用堆棧進行記錄,由于默認的采樣周期設置的是100Mz,所以,你可以理解為每10ms,golang就會統(tǒng)計下當前正在運行的是哪個函數(shù),在采樣的這段時間內,哪個函數(shù)被統(tǒng)計的次數(shù)越多,是不是就能說明這個函數(shù)在這段時間內占用cpu的工作時長就越多了。

由于golang借助了linux的信號機制去進行cpu執(zhí)行函數(shù)的采樣,這里有必要額外介紹下linux 進程與信號相關的知識。首先來看下線程處理信號的時機在什么時候。

線程處理信號的時機

線程對信號的處理時機一般 是在由內核態(tài)返回到用戶態(tài)之前,也就是說,當線程由于系統(tǒng)調用或者中斷進入內核態(tài)后, 當系統(tǒng)調用結束或者中斷處理完成后,在返回到用戶態(tài)之前,操作系統(tǒng)會檢查這個線程是不是有未處理的信號,如果有的話,那么會先切回到用戶態(tài)讓 線程會首先處理信號,信號處理完畢后 又返回內核態(tài),內核此時才會將調用棧設置為中斷或者系統(tǒng)調用時 用戶進程中斷的地方 ,然后切換到用戶態(tài)后就繼續(xù)在用戶進程之前中斷的地方繼續(xù)執(zhí)行程序邏輯了。由于進程幾乎每時每刻都在進行諸如系統(tǒng)調用的工作,可以認為,信號的處理是幾乎實時的。 如下是線程內核態(tài)與用戶態(tài)切換的過程,正式信號處理檢查的地方。整個過程可以用下面的示意圖表示。

知道了信號是如何被線程處理的,還需要了解下,內核會如何發(fā)送信號給進程。

內核發(fā)送信號的方式

內核向進程發(fā)信號的方式是對進程中的一個線程發(fā)送信號,而通過settimmer 系統(tǒng)調用設置定時器 發(fā)送SIGPROF 信號的方式就是隨機的對進程中的一個運行中線程去進行發(fā)送。而運行中線程接收到這個信號后,就調用自身的處理函數(shù)對這個信號去進行處理,對于SIGPROF 信號而言,則是將線程中斷前的函數(shù)棧記錄下來,用于后續(xù)分析函數(shù)占用cpu的工作時長。

由于只是隨機的向一個運行中的線程發(fā)送SIGPROF 信號,這里涉及到了兩個問題?

第一因為同一個進程中只有一個線程在進行采樣,所以在隨機選擇運行線程發(fā)送SIGPROF信號時,要求選擇線程時的公平性,不然可能會出現(xiàn)A,B兩個線程,A線程接收到SIGPROF信號的次數(shù)遠遠大于B 線程接收SIGPROF信號的次數(shù),這樣對A線程進行采樣的次數(shù)將會變多,影響了我們采樣的結果。

而golang用settimmer 設置定時器發(fā)送SIGPROF 信號 的方式的確被證實在linux上存在線程選擇公平性問題(但是mac os上沒有這個問題) 關于這個問題的討論在github上有記錄,這是鏈接 這個問題已經在go1.18上得到了解決,解決方式我會在下面給出,我們先來看隨機的向一個運行中的線程發(fā)送SIGPROF 信號 引發(fā)的第二個問題。

第二 因為是向一個運行中的線程去發(fā)送信號,所以我們只能統(tǒng)計到采樣時間段內在cpu上運行的函數(shù),而那些io阻塞的函數(shù)將不能被統(tǒng)計到,關于這點業(yè)內已經有開源庫幫助解決,https://github.com/felixge/fgprof,不過由于這個庫進行采樣時會stop the world ,所以其作者強烈建議如果go協(xié)程數(shù)量比較多時,將go版本升級到1.19再使用。后續(xù)有機會再來探討這個庫的實現(xiàn)吧,我們先回到如何解決settimer函數(shù)在選擇線程的公平性問題上。

采樣數(shù)據(jù)的公平性

為了解決公平性問題,golang在settimer的系統(tǒng)調用的基礎上增加了timer_create系統(tǒng)調用timer_create 可以單獨的為每一個線程都創(chuàng)建定時器,這樣每個運行線程都會采樣到自己的函數(shù)堆棧了。所以在go1.18版本對pprof.StartCPUProfile內部創(chuàng)建定時器的代碼進行了改造。剛才有提到pprof.StartCPUProfile 底層其實是調用setcpuprofilerate 這個方法去設置的定時器,所以我們來看看go1.18和go1.17版本在這個方法的實現(xiàn)上主要是哪里不同。

// go1.17 版本 src/runtime/proc.go:4563 
func setcpuprofilerate(hz int32) {
	if hz < 0 {
		hz = 0
	}
	_g_ := getg()
	_g_.m.locks++
	setThreadCPUProfiler(0)
	for !atomic.Cas(&prof.signalLock, 0, 1) {
		osyield()
	}
	if prof.hz != hz {
	   // 設置進程維度的 SIGPROF 信號發(fā)送器
		setProcessCPUProfiler(hz)
		prof.hz = hz
	}
	atomic.Store(&prof.signalLock, 0)
	lock(&sched.lock)
	sched.profilehz = hz
	unlock(&sched.lock)
	if hz != 0 {
	   // 設置線程維度的SIGPROF 信號定時器
		setThreadCPUProfiler(hz)
	}
	_g_.m.locks--
}

上述是go1.17版本的setcpuprofilerate 代碼,如果你再去看 go1.18版本的代碼,會發(fā)現(xiàn)他們在這個方法上是一模一樣的,都是調用了setProcessCPUProfiler 和setThreadCPUProfiler,setProcessCPUProfiler 設置進程維度的發(fā)送SIGPROF信號定時器,setThreadCPUProfiler設置線程維度的發(fā)送SIGPROF信號的定時器,但其實setThreadCPUProfiler 在go1.17的實現(xiàn)上并不完整。

// go 1.17  src/runtime/signal_unix.go:314
func setThreadCPUProfiler(hz int32) {
	getg().m.profilehz = hz
}

go1.17版本上僅僅是為協(xié)程里代表線程的m變量設置了一個profilehz(采樣的頻率),并沒有真正實現(xiàn)線程維度的采樣。

// go 1.18 src/runtime/os_linux.go:605 
....
// setThreadCPUProfiler 方法內部 timer_create的代碼段
var timerid int32
	var sevp sigevent
	sevp.notify = _SIGEV_THREAD_ID
	sevp.signo = _SIGPROF
	sevp.sigev_notify_thread_id = int32(mp.procid)
	ret := timer_create(_CLOCK_THREAD_CPUTIME_ID, &sevp, &timerid)
	if ret != 0 {
		return
	}
	....

在go1.18版本上的setThreadCPUProfiler則真正實現(xiàn)了這部分邏輯,由于go1.18版本它同時調用了setProcessCPUProfiler以及setThreadCPUProfiler,這樣在接收SIGPROF信號時就會出現(xiàn)重復計數(shù)的問題。

所以go1.18在處理SIGPROF信號的時候也做了去重處理,所以在golang信號處理的方法sighandler 內部有這樣一段邏輯。

func sighandler(sig uint32, info *siginfo, ctxt unsafe.Pointer, gp *g) {
	_g_ := getg()
	c := &sigctxt{info, ctxt}

	if sig == _SIGPROF {
		mp := _g_.m
		// Some platforms (Linux) have per-thread timers, which we use in
		// combination with the process-wide timer. Avoid double-counting.
		if validSIGPROF(mp, c) {
			sigprof(c.sigpc(), c.sigsp(), c.siglr(), gp, mp)
		}
		return
	}
	.....

如果發(fā)現(xiàn)信號是_SIGPROF 那么會通過validSIGPROF 去檢測此次的_SIGPROF信號是否應該被統(tǒng)計。validSIGPROF的檢測邏輯這里就不展開了。

總結

cpu的統(tǒng)計原理與前面所講的指標統(tǒng)計的原理稍微復雜點,涉及到了linux信號處理相關的內容,cpu統(tǒng)計的原理,簡而言之,就是通過設置一個發(fā)送SIGPROF信號的定時器,然后用戶程序通過接收操作系統(tǒng)定時發(fā)送的SIGPROF信號來對用戶程序正在執(zhí)行的堆棧函數(shù)進行統(tǒng)計。在采樣時間內,同一個函數(shù)被統(tǒng)計的越多,說明該函數(shù)占用的cpu工作時長就越長。

到此這篇關于Golang pprof監(jiān)控之cpu占用率統(tǒng)計原理詳解的文章就介紹到這了,更多相關Golang pprof cpu占用率統(tǒng)計內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Golang等多種語言轉數(shù)組成字符串舉例詳解

    Golang等多種語言轉數(shù)組成字符串舉例詳解

    今天寫代碼遇到數(shù)組轉換成字符串操作,下面這篇文章主要給大家介紹了關于Golang等多種語言轉數(shù)組成字符串的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2023-05-05
  • Go設計模式之原型模式圖文詳解

    Go設計模式之原型模式圖文詳解

    原型模式是一種創(chuàng)建型設計模式, 使你能夠復制已有對象, 而又無需使代碼依賴它們所屬的類,本文將通過圖片和文字讓大家可以詳細的了解Go的原型模式,感興趣的通過跟著小編一起來看看吧
    2023-07-07
  • 詳解Go語言設計模式之單例模式

    詳解Go語言設計模式之單例模式

    單例模式很容易記住。就像名稱一樣,它只能提供對象的單一實例,保證一個類只有一個實例,并提供一個全局訪問該實例的方法。本文就來聊聊Go語言中的單例模式,感興趣的小伙伴可以了解一下
    2022-10-10
  • Go異步任務解決方案之Asynq庫詳解

    Go異步任務解決方案之Asynq庫詳解

    需要在Go應用程序中異步處理任務? Asynq,簡單高效的任務隊列實現(xiàn),下面這篇文章主要給大家介紹了關于Go異步任務解決方案之Asynq庫的相關資料,需要的朋友可以參考下
    2023-02-02
  • Go語言依賴管理三要素示例解析

    Go語言依賴管理三要素示例解析

    這篇文章主要介紹了Go語言依賴管理三要素及示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-01-01
  • Go語言學習之接口類型(interface)詳解

    Go語言學習之接口類型(interface)詳解

    接口是用來定義行為的類型,定義的行為不由接口直接實現(xiàn),而由通過方法由定義的類型實現(xiàn),本文就來和大家詳細講講Go語言中接口的使用吧
    2023-03-03
  • 使用go自定義prometheus的exporter

    使用go自定義prometheus的exporter

    在prometheus中如果要監(jiān)控服務器和應用的各種指標,需要用各種各樣的exporter服務,這篇文章主要介紹了使用go自定義prometheus的exporter,需要的朋友可以參考下
    2023-03-03
  • 使用Go語言編寫一個NTP服務器的流程步驟

    使用Go語言編寫一個NTP服務器的流程步驟

    NTP服務器【Network?Time?Protocol(NTP)】是用來使計算機時間同步化的一種協(xié)議,為了確保封閉局域網內多個服務器的時間同步,我們計劃部署一個網絡時間同步服務器(NTP服務器),本文給大家介紹了使用Go語言編寫一個NTP服務器的流程步驟,需要的朋友可以參考下
    2024-11-11
  • 詳解Golang中select的使用與源碼分析

    詳解Golang中select的使用與源碼分析

    select?是?Go?提供的?IO?多路復用機制,可以用多個?case?同時監(jiān)聽多個?channl?的讀寫狀態(tài)。本文將從源碼角度帶大家了解一下select的使用,需要的可以參考一下
    2022-12-12
  • 分布式架構在Go語言網站的應用

    分布式架構在Go語言網站的應用

    分布式架構是目前應對高流量、高并發(fā)的重要解決方案,分布式架構的核心思想是分而治之,將單臺服務器的資源劃分為多臺服務器進行協(xié)同完成,分布式架構應用于Go語言網站中既能提升服務速度,又能降低了服務器宕機的風險
    2024-01-01

最新評論

中江县| 新和县| 文水县| 灌云县| 新化县| 保定市| 秦安县| 乌兰浩特市| 农安县| 宁河县| 尼勒克县| 阜康市| 区。| 合肥市| 北京市| 武定县| 简阳市| 江城| 新津县| 嘉荫县| 册亨县| 庆云县| 治多县| 兴和县| 青神县| 墨玉县| 敦化市| 临洮县| 辛集市| 鄯善县| 玉树县| 西藏| 通渭县| 屯门区| 元阳县| 砚山县| 梨树县| 玛曲县| 沿河| 土默特左旗| 樟树市|