診斷
簡介
Go 生態系統提供了一大套 API 和工具,用於診斷 Go 程式中的邏輯和效能問題。此頁面概述了可用的工具,並協助 Go 使用者選擇適合其特定問題的工具。
診斷解決方案可分類為下列群組
- 剖析:剖析工具會分析 Go 程式的複雜度和成本,例如記憶體使用量和頻繁呼叫的函式,以找出 Go 程式中昂貴的部分。
- 追蹤:追蹤是一種為程式碼加上工具的方法,用於分析呼叫或使用者要求的生命週期中的延遲。追蹤提供每個組件對系統整體延遲貢獻多少延遲的概觀。追蹤可以跨越多個 Go 程序。
- 偵錯:偵錯允許我們暫停 Go 程式並檢查其執行。程式狀態和流程可以用偵錯來驗證。
- 執行時期統計和事件:收集和分析執行時期統計和事件提供 Go 程式健全性的高階概觀。指標的激增/下降有助於我們找出吞吐量、使用率和效能的變化。
注意:有些診斷工具可能會互相干擾。例如,精確的記憶體剖析會扭曲 CPU 剖析,而 goroutine 阻擋剖析會影響排程器追蹤。請孤立使用工具以取得更精確的資訊。
剖析
剖析有助於找出昂貴或頻繁呼叫的程式碼部分。Go 執行時期提供 剖析資料,格式符合 pprof 視覺化工具 的預期。剖析資料可以在測試期間透過 go
test
或 net/http/pprof 套件提供的端點收集。使用者需要收集剖析資料並使用 pprof 工具來過濾和視覺化頂端的程式碼路徑。
runtime/pprof 套件提供的預定義剖析
- cpu:CPU 剖析會找出程式在積極消耗 CPU 週期時花費時間的地方(與休眠或等待 I/O 時不同)。
- heap:Heap 剖析會報告記憶體配置範例;用於監控目前的和歷史的記憶體使用量,並檢查記憶體洩漏。
- threadcreate:Thread 建立剖析會報告程式中導致建立新的 OS thread 的區段。
- goroutine:Goroutine 剖析會報告所有目前的 goroutine 的堆疊追蹤。
-
block:Block 剖析會顯示 goroutine 在等待同步原語(包括計時器通道)時會在哪裡封鎖。Block 剖析預設不會啟用;請使用
runtime.SetBlockProfileRate
來啟用它。 -
mutex:Mutex 剖析會報告鎖定爭用。當您認為您的 CPU 由於 mutex 爭用而沒有完全使用時,請使用此剖析。Mutex 剖析預設不會啟用,請參閱
runtime.SetMutexProfileFraction
來啟用它。
我可以使用哪些其他剖析器來剖析 Go 程式?
在 Linux 上,perf 工具可用於剖析 Go 程式。Perf 可以剖析和解開 cgo/SWIG 程式碼和核心,因此它可用於深入了解原生/核心效能瓶頸。在 macOS 上,Instruments 套件可用於剖析 Go 程式。
我可以剖析我的生產服務嗎?
是。在生產環境中分析程式是安全的,但啟用某些分析 (例如 CPU 分析) 會增加成本。您應該預期會看到效能下降。效能損失可以在生產環境中開啟分析器之前,透過測量分析器的負擔來估計。
您可能想要定期分析您的生產服務。特別是在具有單一程序許多複製品的系統中,定期選擇一個隨機複製品是一個安全的選項。選擇一個生產程序,每隔 Y 秒對其進行 X 秒的分析,並儲存結果以進行視覺化和分析;然後定期重複。可以手動和/或自動檢閱結果以找出問題。收集分析可能會互相干擾,因此建議一次只收集一個分析。
視覺化分析資料的最佳方法是什麼?
Go 工具使用 go tool pprof
提供分析資料的文字、圖形和 callgrind 視覺化。閱讀 分析 Go 程式 以查看它們的作用。
以文字列出最昂貴的呼叫。
以圖形視覺化最昂貴的呼叫。
Weblist 檢視會在 HTML 頁面中逐行顯示來源的昂貴部分。在以下範例中,530ms 花費在 runtime.concatstrings
中,且每個行的成本都顯示在清單中。
以 Weblist 視覺化最昂貴的呼叫。
視覺化分析資料的另一種方式是 火焰圖。火焰圖讓您可以在特定的祖先路徑中移動,因此您可以放大/縮小特定程式碼區段。上游 pprof 支援火焰圖。
火焰圖提供視覺化以找出最昂貴的程式碼路徑。
我是否受限於內建分析?
除了執行時期提供的內容之外,Go 使用者可以透過 pprof.Profile 建立自訂設定檔,並使用現有的工具來檢查這些設定檔。
我可以在不同的路徑和埠上提供設定檔處理常式 (/debug/pprof/...) 嗎?
可以。net/http/pprof
套件預設會將其處理常式註冊到預設混合器,但您也可以使用從套件匯出的處理常式自行註冊。
例如,以下範例會在 :7777 上的 /custom_debug_path/profile 提供 pprof.Profile 處理常式
package main import ( "log" "net/http" "net/http/pprof" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/custom_debug_path/profile", pprof.Profile) log.Fatal(http.ListenAndServe(":7777", mux)) }
追蹤
追蹤是一種編寫工具程式碼的方式,用於分析呼叫鏈的整個生命週期中的延遲。Go 提供 golang.org/x/net/trace 套件作為每個 Go 節點的最小追蹤後端,並提供一個最小編寫工具程式碼庫,其中包含一個簡單的儀表板。Go 還提供一個執行追蹤器,用於追蹤區間內的執行時期事件。
追蹤讓我們能夠
- 編寫工具程式碼並分析 Go 程序中的應用程式延遲。
- 衡量在長呼叫鏈中特定呼叫的成本。
- 找出使用率和效能改善之處。沒有追蹤資料,瓶頸並不總是顯而易見的。
在單體系統中,從程式碼區塊收集診斷資料相對容易。所有模組都存在於單一程序中,並共用記錄檔、錯誤和其他診斷資訊的共用資源。一旦系統擴展到單一程序之外並開始分散,就更難追蹤從前端網路伺服器開始到所有後端的所有呼叫,直到回應傳回給使用者。這正是分散式追蹤在分析和分析生產系統中扮演重要角色的地方。
分散式追蹤是一種分析使用者要求生命週期中延遲的程式碼工具。當系統分散且傳統的設定檔和除錯工具無法擴充時,您可能想使用分散式追蹤工具來分析使用者要求和 RPC 的效能。
分散式追蹤使我們能夠
- 在大型系統中分析和設定檔應用程式延遲。
- 追蹤使用者要求生命週期中的所有 RPC,並查看僅在生產中可見的整合問題。
- 找出可套用於我們系統的效能改善。在收集追蹤資料之前,許多瓶頸並不明顯。
Go 生態系統提供各種分散式追蹤程式庫,每個追蹤系統和後端不可知。
是否有辦法自動攔截每個函式呼叫並建立追蹤?
Go 沒有提供自動攔截每個函式呼叫並建立追蹤區間的方法。您需要手動設定程式碼才能建立、結束和註解區間。
我應該如何在 Go 程式庫中傳播追蹤標頭?
您可以在 context.Context
中傳播追蹤識別碼和標籤。目前業界尚未有規範的追蹤金鑰或追蹤標頭的共用表示法。每個追蹤提供者負責在其 Go 程式庫中提供傳播工具程式。
還有哪些來自標準程式庫或執行時期的低階事件可以包含在追蹤中?
標準函式庫和執行階段嘗試公開數個額外的 API,以在低層級內部事件中發出通知。例如,httptrace.ClientTrace
提供 API 來追蹤傳出要求生命週期中的低層級事件。目前正持續努力從執行階段執行追蹤器中擷取低層級執行階段事件,並允許使用者定義和記錄其使用者事件。
除錯
除錯是識別程式為何發生異常行為的程序。除錯工具讓我們了解程式的執行流程和目前狀態。有數種除錯方式;本節只會專注於將除錯工具附加至程式和核心傾印除錯。
Go 使用者大多使用下列除錯工具
- Delve:Delve 是 Go 程式語言的除錯工具。它支援 Go 的執行階段概念和內建類型。Delve 嘗試成為 Go 程式的功能齊全且可靠的除錯工具。
- GDB:Go 透過標準 Go 編譯器和 Gccgo 提供 GDB 支援。堆疊管理、執行緒和執行階段包含與 GDB 預期的執行模式有足夠差異的層面,即使程式是使用 gccgo 編譯的,它們也可能讓除錯工具感到混淆。即使 GDB 可用於除錯 Go 程式,但它並非理想,且可能會造成混淆。
除錯工具與 Go 程式搭配使用時,運作得如何?
gc
編譯器會執行最佳化,例如函式內聯和變數暫存器化。這些最佳化有時會讓除錯工具更難以除錯。目前正持續努力改善為最佳化二進位檔產生的 DWARF 資訊品質。在這些改善措施推出之前,我們建議在建置要除錯的程式碼時停用最佳化。下列指令會建置一個沒有任何編譯器最佳化的套件
$ go build -gcflags=all="-N -l"作為改善措施的一部分,Go 1.10 引進新的編譯器旗標
-dwarflocationlists
。此旗標會讓編譯器加入位置清單,以協助除錯工具處理最佳化的二進位檔。下列指令會建置一個有最佳化但有 DWARF 位置清單的套件
$ go build -gcflags="-dwarflocationlists=true"
建議的除錯工具使用者介面是什麼?
儘管 delve 和 gdb 都提供 CLI,但大多數編輯器整合和 IDE 都提供特定於偵錯的使用者介面。
是否可以對 Go 程式執行事後偵錯?
核心傾印檔是一個包含執行中程序的記憶體傾印和程序狀態的檔案。它主要用於程式的死後偵錯,並了解其在執行中的狀態。這兩種情況使核心傾印的偵錯成為死後診斷和分析生產服務的良好診斷輔助工具。可以從 Go 程式取得核心檔案,並使用 delve 或 gdb 進行偵錯,請參閱 核心傾印偵錯 頁面以取得逐步指南。
執行時期統計和事件
執行時期提供統計和內部事件的報告,供使用者診斷執行時期層級的效能和使用率問題。
使用者可以監控這些統計資料,以更深入了解 Go 程式的整體健全性和效能。一些經常監控的統計資料和狀態
runtime.ReadMemStats
報告與堆疊配置和垃圾回收相關的指標。記憶體統計資料對於監控程序消耗多少記憶體資源、程序是否能有效利用記憶體以及偵測記憶體外洩很有用。debug.ReadGCStats
讀取有關垃圾回收的統計資料。它有助於了解有多少資源花費在 GC 暫停上。它還會報告垃圾收集器暫停的時間軸和暫停時間百分比。debug.Stack
傳回目前的堆疊追蹤。堆疊追蹤有助於了解目前有多少個 goroutine 正在執行、它們在做什麼,以及它們是否被封鎖。debug.WriteHeapDump
暫停所有 goroutine 的執行,並允許你將堆疊傾印到檔案中。堆疊傾印是 Go 程序在特定時間的記憶體快照。它包含所有配置的物件以及 goroutine、完成處理常式等。runtime.NumGoroutine
會傳回目前 goroutine 的數量。可以監控這個值,以查看是否已使用足夠的 goroutine,或偵測 goroutine 遺漏。
執行追蹤器
Go 附帶一個執行時期執行追蹤器,用於擷取各種執行時期事件。排程、系統呼叫、垃圾回收、堆積大小和其他事件會由執行時期收集,並可供 go tool trace 視覺化。執行追蹤器是一個偵測延遲和使用率問題的工具。您可以檢查 CPU 的使用率,以及網路或系統呼叫何時會導致 goroutine 被搶先。
追蹤器可協助
- 了解您的 goroutine 如何執行。
- 了解一些核心執行時期事件,例如 GC 執行。
- 找出平行化執行不佳之處。
但是,它並不適合找出熱點,例如分析造成過度記憶體或 CPU 使用率的原因。請改用剖析工具來處理這些問題。
上方 go tool trace 視覺化顯示執行一開始正常,然後變成序列化。這表示可能會因共用資源的鎖定競爭而造成瓶頸。
請參閱 go
tool
trace
以收集和分析執行時期追蹤。
GODEBUG
如果 GODEBUG 環境變數設定適當,執行時期也會發出事件和資訊。
- GODEBUG=gctrace=1 會在每次收集時列印垃圾收集器事件,摘要收集的記憶體量和暫停時間。
- GODEBUG=inittrace=1 會列印已完成套件初始化工作的執行時間和記憶體配置資訊摘要。
- GODEBUG=schedtrace=X 會每隔 X 毫秒列印排程事件。
GODEBUG 環境變數可用於停用標準函式庫和執行時期中指令集擴充功能的使用。
- GODEBUG=cpu.all=off 會停用所有選用指令集擴充功能的使用。
- GODEBUG=cpu.extension=off 會停用指定指令集擴充功能中的指令使用。
extension 是指令集擴充功能的小寫名稱,例如 sse41 或 avx。