Go36-48,49-程式效能分析基礎
程式效能分析基礎
本篇講的是Go程式的效能分析,下面提到的內容都是從事這項任務必備的一些知識和技巧。這些有助於我們真正理解以取樣、收集、輸出為代表的一系列操作步驟。
程式碼包
Go語言為程式開發者們提供了豐富的效能分析API,和非常好用的標準工具。這些API主要存在於下面三個包中:
- runtime/pprof
- net/http/pprof
- runtime/trace
另外,runtime包中還包含了一些更底層的API。這些都可以被用來收集或輸出Go程式執行過程中的一些關鍵指標,並幫助我們生成相應的概要檔案以供後續分析時使用。
標準工具
標準工具主要有:
- go tool pprof
- go tool trace
這兩個工具,可以解析概要檔案中的資訊,並以人類易讀的方式把這些資訊展示出來。
go test命令,也可以在程式測試完成後生成概要檔案。這樣就可以很方便的使用前面那兩個工具讀取概要檔案,並對被測程式的效能加以分析。這樣就讓程式效能測試的資料更加豐富,結果也更加精確和可信。
概要檔案
在Go語言中,用於分析程式效能的概要檔案有三種:
- CPU Profile : CPU概要檔案
- Men Profile : 記憶體概要檔案
- Block Profile : 阻塞概要檔案
這些概要檔案中包含的都是:在某一段時間內,對Go程式的相關指標進行多次取樣後得到的概要資訊。
對於CPU概要檔案 ,其中的每一段獨立的概要資訊都記錄著在進行某一次取樣的那個時刻,CPU上正在執行的Go程式碼。
對於記憶體概要檔案 ,其中的每一段概要資訊都記載著在某個取樣時刻,正在執行的Go程式碼以及堆記憶體的使用請求,這裡包含已分配和已釋放的位元組數量和物件數量。
對於阻塞概要檔案 ,其中每一段概要資訊都代表著Go程式中的一個goroutine的阻塞事件。
檢視概要檔案在預設情況下,這些概要檔案中的資訊並不是普通的文字,它們是以二進位制的形式展現的。如果使用常規的文字編輯器檢視,看到的是亂碼。需要用go tool pprof這個工具來檢視。可以通過該工具進入一個基於命令列的互動式介面,並對指定的概要檔案進行查閱:
$ go tool pprof cpuprofile.out Type: cpu Time: Nov 9, 2018 at 4:31pm (CST) Duration: 7.96s, Total samples = 6.88s (86.38%) Entering interactive mode (type "help" for commands, "o" for options) (pprof)
關於這個工具的具體用法沒有展開。建議在使用時,輸入help檢視幫助資訊。
Protocol Buffers
概要檔案中的資訊並不是普通的文字。而是通過protocol buffers生成的二進位制資料流,或者說位元組流。而protocol buffers是一種資料序列化協議,同時也是一個序列化工具。它可以把一個值,比如一個結構體或者一個字典,轉換成一段位元組流。這個過程叫序列化。也可以反過來,把生成的位元組流轉換為程式中的一個值,這叫反序列化。
Go語言從1.8版本開始,把所有的profile相關的資訊生成工作都交給protocol buffers來做了。它有不少的優勢。可以在序列化資料的同時對資料進行壓縮,所以生成的位元組流通常都要比其他格式(XML和JSON)佔用的空間小很多。還支援自定義資料序列化和結構化的格式,也允許在保證向後相容的前提下更新這種格式。這就是概要檔案不使用普通文字格式儲存的原因。
順便提一下,protocol buffers的用途非常廣泛,並且在諸如資料儲存、資料傳輸等任務中有著很高的使用率。
Protocol Buffers,是Google公司開發的一種資料描述語言,類似於XML能夠將結構化資料序列化,可用於資料儲存、通訊協議等方面。
更多相關的知識就不展開的。
CPU概要資訊
取樣CPU概要資訊,需要用到runtime/pprof包中的API。要讓程式開始對CPU概要資訊進行取樣,需要呼叫包中的StartCPUProfile函式。而在停止取樣的時候,需要呼叫包中的StopCPUProfile函式。
StartCPUProfile函式
runtime/pprof.StartCPUProfile函式在被呼叫的時候,先會去設定CPU概要資訊的取樣頻率,並會在單獨的goroutine中執行CPU概要資訊的收集和輸出。StartCPUProfile函式設定的取樣頻率總是固定的100Hz,就是每秒取樣100次,或者說每10毫秒取樣一次。
關於CPU的主頻CPU的主頻是CPU核心工作的時鐘頻率,也常被稱為:CPU clock speed。這個時鐘頻率的倒數即為時鐘週期(clock cycle),也就是一個CPU核心執行一條運算指令所需的時間,單位秒。例如:主頻為1000Hz的CPU,它的單個核心執行一條運算指令所需的時間為0.001秒,即1毫秒。又例如,現在常見的3.2GHz的多核CPU,其單個核心在1納秒的時間裡就可以至少執行三條運算指令。
StartCPUProfile函式設定的CPU概要資訊取樣頻率,相對於現代的CPU主頻來說是非常低的。這主要有兩個方面的原因。
一、過高的取樣頻率會對Go程式的執行效率造成很明顯的負面影響。因此,runtime包中StartCPUProfileRate函式在被呼叫的時候,會保證取樣頻率不超過1MHz,也就是隻允許1微妙最多采樣一次。StartCPUProfile函式正是通過呼叫這個函式來設定CPU概要資訊的取樣頻率的。
二、經過大量的實現,GO語言團隊發現100Hz是一個比較合適的設定。因為這樣做既可以得到足夠多、足夠有用的概要資訊,又不至於讓程式的執行出現停滯。另外,作業系統對高頻取樣的處理能力也是有限的,一般情況下,超過500Hz就很可能得不到及時的響應的。
StopCPUProfile函式
在StartCPUProfile函式執行之後,一個新啟用的goroutine將會負責執行CPU概要資訊的收集和輸出,直到runtime/pprof包中的StopCPUProfile函式被成功呼叫。
StopCPUProfile函式也會呼叫runtime.SetCPUProfileRate函式,並把引數值就是取樣頻率設為0。這會讓針對CPU概要資訊的取樣工作停止。同時還會給負責收集CPU概要資訊的程式碼一個訊號,告知收集工作也需要停止。在接到訊號之後,那部分程式將會把這段時間內收集到的所有CPU概要資訊,全部寫入到我們在呼叫StartCPUProfile函式的時候指定的寫入器中。只有在上述操作全部完成之後,StopCPUProfile函式才會返回。
編寫取樣程式碼
上面已經分析了,首先要呼叫StartCPUProfile函式,要停止的時候就呼叫StopCPUProfile函式。中間就是需要進行測試的程式碼:
func main() { // 開啟檔案,準備寫入 filename := "cpuprofile2.out" f, err := os.Create(filename) if err != nil { fmt.Fprintf(os.Stderr, "Create File Error: %v", err) return } defer f.Close() // 進行取樣 if err := startCPUProfile(f); err != nil { fmt.Fprintf(os.Stderr, "CPU profile start error: %v\n", err) return } /* 這裡寫需要測試的程式碼 */ // 停止取樣 stopCPUProfile() } func startCPUProfile(w io.Writer) error { if w == nil { return errors.New("nil File") } return pprof.StartCPUProfile(w) } func stopCPUProfile() { pprof.StopCPUProfile() }
被測試的程式碼下面這段程式,應該就是純粹為了看效果,是一段CPU密集型操作的程式碼:
// article48/common/op/cpu.go package op import ( "bytes" "math/rand" "strconv" ) func CPUProfile() error { max := 10000000 var buf bytes.Buffer for i := 0; i < max; i++ { num := rand.Int63n(int64(max)) str := strconv.FormatInt(num, 10) buf.WriteString(str) } _ = buf.String() return nil }
包裝被測試的函式這裡再額外做一步,對上面的函式進行一次包裝,可以執行多次被測試的函式。所以下面要實現的函式要傳入兩個引數,一個是被測試的函式,一個是希望執行的次數:
// article48/common/common.go package common import ( "errors" "fmt" "time" ) // 代表包含高負載操作的函式 type OpFunc func() error func Execute(op OpFunc, times int) (err error) { if op == nil { return errors.New("操作函式為nil") } if times <= 0 { return fmt.Errorf("執行次數不可用: %d", times) } var startTime time.Time defer func() { diff := time.Now().Sub(startTime) fmt.Printf("執行持續時間: %s\n", diff) if p := recover(); p != nil { err = fmt.Errorf("fatal error: %v", p) } }() startTime = time.Now() for i := 0; i < times; i++ { if err = op(); err != nil { return } time.Sleep(time.Microsecond) } return }
這個函式是要準備複用的。之後還會進行記憶體概要和阻塞概要的測試,也會有對應的測試程式碼。不過函式的簽名都將是一樣的:type OpFunc func() error
。
完成測試
上面已經有了完整的被測試函式,以及包裝被測試函式的函式。這裡把之前不完整的取樣測試的程式碼再補充完整:
package main import ( "Go36/article48/common" "Go36/article48/common/op" "errors" "fmt" "io" "os" "runtime/pprof" ) func main() { // 開啟檔案,準備寫入 filename := "cpuprofile.out" f, err := os.Create(filename) if err != nil { fmt.Fprintf(os.Stderr, "Create File Error: %v", err) return } defer f.Close() // 進行取樣 if err := startCPUProfile(f); err != nil { fmt.Fprintf(os.Stderr, "CPU profile start error: %v\n", err) return } // 被測試的函式 if err := common.Execute(op.CPUProfile, 10); err != nil { fmt.Fprintf(os.Stderr, "execute error: %v\n", err) return } // 停止取樣 stopCPUProfile() } func startCPUProfile(w io.Writer) error { if w == nil { return errors.New("nil File") } return pprof.StartCPUProfile(w) } func stopCPUProfile() { pprof.StopCPUProfile() }
現在可以執行上面的程式,生成效能分析報告:
PS H:\Go\src\Go36\article48\example01> go run main.go 執行持續時間: 8.3462144s PS H:\Go\src\Go36\article48\example01>
執行後會生成一個二進位制檔案,需要用go tool pprof來檢視
PS H:\Go\src\Go36\article48\example01> go tool pprof cpuprofile.out Type: cpu Time: Feb 12, 2019 at 7:33pm (CST) Duration: 8.45s, Total samples = 8.50s (100.59%) Entering interactive mode (type "help" for commands, "o" for options) (pprof)
記憶體概要資訊
針對記憶體概要資訊的取樣會按照一點比例收集Go程式在執行期間的堆記憶體使用情況。
取樣頻率
設定記憶體概要資訊取樣頻率的方法很簡單,只要為runtime.MemProfileRate變數賦值即可。
這個變數的含義是,平均每分配多少個位元組,就對堆記憶體的使用情況進行一次取樣。如果把該變數的值設為0,那麼,Go語言執行時系統就會完全停止對記憶體概要資訊的取樣。該變數的預設值是512KB,即512千位元組。如果要設定這個取樣頻率,就要越早越好,並且只應該設定一次,否則就可能會對採集工作造成不良影響。比如,只在main函式的開始處設定一次。
之後,要獲取記憶體概要資訊,還需要呼叫WriteHeapProfile函式。該函式會把收集好的記憶體概要資訊寫到指定的寫入器中。通過WriteHeapProfile函式得到的記憶體概要資訊並不是實時的,它是一個快照,是在最近一次的記憶體垃圾收集工作完成時產生的。如果想要實時的資訊,那麼可以呼叫runtime.ReadMemStats函式。不過要特別注意,該函式會引起Go語言排程器的短暫停頓。
記憶體測試函式
複用之前的common程式,這裡需要一個會分配很多記憶體的測試程式碼:
// article48/common/op/cpu.go package op import ( "bytes" "encoding/json" "math/rand" ) // box 代表資料盒子。 type box struct { Strstring Coderune Bytes []byte } func MemProfile() error { max := 50000 var buf bytes.Buffer for j := 0; j < max; j++ { seed := rand.Intn(95) + 32 one := createBox(seed) b, err := genJSON(one) if err != nil { return err } buf.Write(b) buf.WriteByte('\t') } _ = buf.String() return nil } func createBox(seed int) box { if seed <= 0 { seed = 1 } var array []byte size := seed * 8 for i := 0; i < size; i++ { array = append(array, byte(seed)) } return box{ Str:string(seed), Code:rune(seed), Bytes: array, } } func genJSON(one box) ([]byte, error) { return json.Marshal(one) }
完成測試
用下面的示例來執行這個測試:
package main import ( "errors" "fmt" "os" "Go36/article48/common" "Go36/article48/common/op" "runtime" "runtime/pprof" ) var memProfileRate = 8 func main() { filename := "memprofile.out" f, err := os.Create(filename) if err != nil { fmt.Fprintf(os.Stderr, "Create File Error: %v", err) return } defer f.Close() startMemProfile() if err := common.Execute(op.MemProfile, 10); err != nil { fmt.Fprintf(os.Stderr, "execute error: %v\n", err) return } if err := stopMemProfile(f); err != nil { fmt.Fprintf(os.Stderr, "memory profile stop error: %v\n", err) return } } func startMemProfile() { runtime.MemProfileRate = memProfileRate } func stopMemProfile(f *os.File) error { if f == nil { return errors.New("nil file") } return pprof.WriteHeapProfile(f) }
阻塞概要資訊
呼叫SetBlockProfileRate函式,即可對阻塞概要資訊的取樣頻率進行設定。
引數設定
SetBlockProfileRate函式的引數rate是int型別。這個引數的含義是,只要發現一個阻塞事件的持續時間達到了rate納秒,就可以對其進行取樣。如果這個引數的值小於或等於0,就會完全停止對阻塞概要資訊的取樣。
另外還有一個blockprofilerate的包級私有變數uint64型別。這個變數的含義是,只要發現一個阻塞事件的持續時間跨越了多少個CPU時鐘週期,就可以對其進行取樣。這個變數的值是自動的通過rate引數來進行設定的。
這兩個變數的區別僅僅是單位不同。SetBlockProfileRate函式會先對引數的rate值進行單位換算和必要的型別轉換,然後,把換算的結果用原子操作賦值給blockprofilerate變數。由於此變數的預設值是0,所以預設情況下不記錄任何阻塞事件。
獲取資訊
在需要獲取阻塞概要資訊的時候,要先呼叫Lookup函式,函式原始碼如下:
func Lookup(name string) *Profile { lockProfiles() defer unlockProfiles() return profiles.m[name] }
這個函式下面會再詳細講,目前只要傳入"block"作為引數值。這裡的"block"代表因爭用同步原語而被阻塞的那些程式碼的堆疊跟蹤資訊,就是阻塞概要資訊。該函式呼叫後會得到一個*Profile型別的值,就是Profile值。在這之後還需要呼叫這個Profile值的WriteTo方法,以驅使它把概要資訊寫進指定的寫入器中。
這個WriteTo方法有兩個引數,原始碼比較長,擷取簽名的部分:
func (p *Profile) WriteTo(w io.Writer, debug int) error { // 省略程式實體 }
第一個引數是寫入器,而第二個引數是代表概要資訊詳細程度的int型別引數debug。debug引數的可選值有三個,0、1或2:
- debug為0,通過WriteTo方法寫進寫入器的概要資訊僅會包含go tool pprof工具所需的記憶體地址,這些記憶體地址會以十六進位制的形式展現出來。並且概要資訊是二進位制位元組流。
- debug為1,相應的包名、函式名、原始碼檔案路徑、程式碼行號等資訊都會作為註釋被加入進去。並且概要資訊是普通文字。
- debug為2,應該還包括大於2的情況,輸出通常會包含更多的細節。至於具體是哪些細節內容,就要看Lookup函式傳入的引數值了。概要資訊還是普通文字。
阻塞測試函式
用下面的函式來測試阻塞:
package op import ( "math/rand" "sync" "time" ) func BlockProfile() error { max := 100 senderNum := max / 2 receiverNum := max / 4 ch1 := make(chan int, max/4) var senderGroup sync.WaitGroup senderGroup.Add(senderNum) repeat := 50000 for j := 0; j < senderNum; j++ { go send(ch1, &senderGroup, repeat) } go func() { senderGroup.Wait() close(ch1) }() var receiverGroup sync.WaitGroup receiverGroup.Add(receiverNum) for j := 0; j < receiverNum; j++ { go receive(ch1, &receiverGroup) } receiverGroup.Wait() return nil } func send(ch1 chan int, wg *sync.WaitGroup, repeat int) { defer wg.Done() time.Sleep(time.Millisecond * 10) for k := 0; k < repeat; k++ { elem := rand.Intn(repeat) ch1 <- elem } } func receive(ch1 chan int, wg *sync.WaitGroup) { defer wg.Done() for elem := range ch1 { _ = elem } }
完成測試
執行下面的示例中的程式碼,可以生成阻塞概要檔案:
package main import ( "errors" "fmt" "os" "Go36/article48/common" "Go36/article48/common/op" "runtime" "runtime/pprof" ) var ( blockProfileRate = 2 debug= 0 ) func main() { filename := "blockprofile.out" f, err := os.Create(filename) if err != nil { fmt.Fprintf(os.Stderr, "Create File Error: %v", err) return } defer f.Close() startBlockProfile() if err := common.Execute(op.BlockProfile, 10); err != nil { fmt.Fprintf(os.Stderr, "execute error: %v\n", err) return } if err := stopBlockProfile(f); err != nil { fmt.Fprintf(os.Stderr, "block profile error: %v\n", err) return } } func startBlockProfile() { runtime.SetBlockProfileRate(blockProfileRate) } func stopBlockProfile(f *os.File) error { if f == nil { return errors.New("nil file") } return pprof.Lookup("block").WriteTo(f, debug) }
更多概要資訊
這裡討論debug為2時的情況,此時就要根據Lookup函式的引數值來決定輸出的細節內容了。
Lookup函式的功能是,提供與給定的名稱相對應的概要資訊。這個概要資訊會由一個Profile值代表。如果該函式返回一個nil,那麼就說明不存在與給定名稱對應的概要資訊。runtime/pprof包已經預先定義了6個概要名稱。它們對應的概要資訊收集方法和輸出方法也都已經準備好了。這裡直接拿來使用就可以了,把預定義好的名稱傳給name引數。具體是下面這些:
//goroutine- stack traces of all current goroutines //heap- a sampling of memory allocations of live objects //allocs- a sampling of all past memory allocations //threadcreate - stack traces that led to the creation of new OS threads //block- stack traces that led to blocking on synchronization primitives //mutex- stack traces of holders of contended mutexes
goroutine
收集當前正在使用的所有goroutine的堆疊跟蹤資訊。注意,這樣的收集會引起Go語言排程器的短暫停頓。
呼叫該函式返回的Profile值的WriteTo方法時,如果引數debug的值大於或等於2,那麼該方法就會輸出所有goroutine的堆疊跟蹤資訊。這些資訊可能會非常多。如果它們佔用的空間超過了64M,那麼相應的方法就會將超出的部分截掉。
heap
收集與堆記憶體的分配和釋放有關的取樣資訊。實際就是之前討論的記憶體概要資訊。
Lookup函式返回的Profile值的WriteTo方法被呼叫時,輸出的記憶體概要資訊預設以“在用空間”(inuse_space)的視角呈現。
在用空間,指已經被分配但還未被釋放的記憶體空間。在這個視角下,go tool pprof工具並不會去理會已釋放空間有關的那部分資訊。
allocs
和上面的heap非常相似,也是收集與堆記憶體的分配和釋放有關的取樣資訊,就是記憶體概要資訊。
Lookup函式返回的Profile值的WriteTo方法被呼叫時,輸出的記憶體概要資訊預設以“已分配空間”(alloc_space)的視角呈現。
已分配空間,是所有的記憶體分配資訊都會被呈現出來,無論這些記憶體空間在取樣時是否已經被釋放。
與heap的差別
差別只是debug引數為0時,WriteTo方法輸出的概要資訊會有細微的差別。如果debug大於0,那麼輸出的內容是完全相同的。
threadcreate
收集堆疊跟蹤資訊時,這些堆疊跟蹤資訊中的每一個都會描繪出一個程式碼呼叫鏈,這些呼叫鏈上的程式碼都導致新的作業系統執行緒產生。這樣的Profile值的輸出規格只有兩種,取決於WriteTo方法的debug引數是否大於0。
block
是因爭用同步原語而被阻塞的那些程式碼的堆疊跟蹤資訊。就是之前討論的阻塞概要資訊。這裡輸出規格只有兩種,取決於debug是否大於0。
mutex
是曾經作為同步原語持有者的那些程式碼,它們的堆疊跟蹤資訊。輸出規格也只有兩種,取決於debug是否大於0。
同步原語這裡所說的同步原語,指的是存在於Go語言執行時系統內部的一種底層的同步工具,或者說一種同步機制。它是直接面向記憶體地址的,並以非同步訊號量和原子操作作為實現手段。通道、互斥鎖、條件變數、WatiGroup,以及Go語言執行時系統本身,都會利用它來實現自己的功能。
生成各種概要資訊
在之前的測試程式碼的基礎上,下面分別呼叫Lookup函式的每一個引數並且分別在debug是0、1、2時各執行了一次,生成了所有可能的概要資訊的檔案:
package main import ( "Go36/article48/common" "Go36/article48/common/op" "fmt" "os" "runtime" "runtime/pprof" "time" ) // profileNames 代表概要資訊名稱的列表。 var profileNames = []string{ "goroutine", "heap", "allocs", "threadcreate", "block", "mutex", } // profileOps 代表為了生成不同的概要資訊而準備的負載函式的字典。 var profileOps = map[string]common.OpFunc{ "goroutine":op.BlockProfile, "heap":op.MemProfile, "allocs":op.MemProfile, "threadcreate": op.BlockProfile, "block":op.BlockProfile, "mutex":op.BlockProfile, } // debugOpts 代表debug引數的可選值列表。 var debugOpts = []int{ 0, 1, 2, } func main() { prepare() for _, name := range profileNames { for _, debug := range debugOpts { err := genProfile(name, debug) if err != nil { return } time.Sleep(time.Millisecond) } } } func genProfile(name string, debug int) error { fmt.Printf("Generate %s profile (debug: %d) ...\n", name, debug) filename := fmt.Sprintf("%s_%d.out", name, debug) f, err := os.Create(filename) if err != nil { fmt.Fprintf(os.Stderr, "Create File Error: %v", err) return err } defer f.Close() if err = common.Execute(profileOps[name], 10); err != nil { fmt.Fprintf(os.Stderr, "execute error: %v (%s)\n", err, filename) return err } profile := pprof.Lookup(name) err = profile.WriteTo(f, debug) if err != nil { fmt.Fprintf(os.Stderr, "write error: %v (%s)\n", err, filename) return err } return nil } func prepare() { runtime.MemProfileRate = 8 runtime.SetBlockProfileRate(2) }
效能分析網路介面
針對上層的應用,為基與HTTP協議的網路服務,新增效能分析介面。
這裡做的是為之前的效能分析提供Web的瀏覽介面。上面生成的效能分析報告需要通過檔案瀏覽器訪問文字內容。通過這裡的Web介面,則直接開啟一個Web服務,直接用瀏覽器訪問來瀏覽各種效能分析報告。
基本用法
在一般情況下只要在程式中匯入net/http/pprof包就可以了:
import _ "net/http/pprof"
然後啟動網路服務並開始監聽:
log.Println(http.ListenAndServe("localhost:8082", nil))
在執行這個程式之後,就可以在瀏覽器中訪問下面的地址:
http://localhost:8082/debug/pprof
訪問後會得到一個簡約的網頁。點選不同的連線,可以看到各種概要資訊,這裡自動就生成所有種類的概要資訊了。
debug引數每個子路徑點進去就會看到這個種類的概要資訊。這裡url還有一個debug引數,這就是之前所講的WriteTo方法裡的debug引數。預設點進去都是1,可以改成別的引數。如果是2就是詳細資訊。如果是0就是二進位制資訊,這時是無法瀏覽的,而是會觸發下載。
gc引數另外還可以給url傳一個gc引數,效果是控制是否在獲取概要資訊之前強制執行一次垃圾回收。只要它的值大於0,程式就會這樣做。不過,這個引數僅對heap有效,就是僅在/debug/pprof/heap路徑下有效。
CPU概要資訊
一旦/debug/pprof/profile路徑被訪問,程式就會去執行對CPU概要資訊的取樣。它接受一個seconds的查詢引數,就是取樣工作需要持續多少秒。如果引數未被顯式指定,那麼取樣工作會持續30秒。所以一旦點下該連線,就會卡住,直到完成取樣。
另外,這裡只會響應經protocol buffers轉換的位元組流,所以取樣完成後,會觸發下載。另外還可以通過go tool pprof工具直接讀取這樣的HTTP響應:
go tool pprof http://localhost:8082/debug/pprof/profile?seconds=60
runtime/trace
這個Web頁面還有一個路徑,/debug/pprof/trace。在這個路徑下,程式主要會利用runtime/trace包中的API來處理請求。
程式會先呼叫trace.Start函式,然後在查詢引數seconds指定的持續時間之後再呼叫trace.Stop函式。這裡的seconds的預設值是1秒。而runtime/trace包的功用並沒有展開。
定製URL
還可以定製URL,下面是一個定製的示例:
package main import ( "log" "net/http" "net/http/pprof" "strings" ) func main() { mux := http.NewServeMux() pathPrefix := "/d/pprof/" mux.HandleFunc(pathPrefix, func(w http.ResponseWriter, r *http.Request) { name := strings.TrimPrefix(r.URL.Path, pathPrefix) if name != "" { pprof.Handler(name).ServeHTTP(w, r) return } pprof.Index(w, r) }) mux.HandleFunc(pathPrefix+"cmdline", pprof.Cmdline) mux.HandleFunc(pathPrefix+"profile", pprof.Profile) mux.HandleFunc(pathPrefix+"symbol", pprof.Symbol) mux.HandleFunc(pathPrefix+"trace", pprof.Trace) server := http.Server{ Addr:"localhost:8083", Handler: mux, } if err := server.ListenAndServe(); err != nil { if err == http.ErrServerClosed { log.Println("HTTP server closed.") } else { log.Printf("HTTP server error: %v\n", err) } } }
在這裡例子中,定製mux的程式碼與包中的init函式很型別。預設的路徑就是在init函式裡實現的。並且之前直接用佔位符匯入net/http/pprof包的時候,就是執行這個init函式而生成了預設的訪問路徑。
小結
在這裡,使用net/http/pprof包要比直接使用runtime/pprof包方便和實用很多。通過合理運用,這個程式碼包可以為網路服務的監測提供有力的支撐。