runtime/trace

golang.org/x/exp/trace

飛行記錄(flight recording)

// 設定飛行記錄器
fr := trace.NewFlightRecorder()
fr.Start()
// 設定並啟動 HTTP 伺服器
var once sync.Once
http.HandleFunc("/my-endpoint", func(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    // 做些事情
    doWork(w, r)
    // 碰到長耗時請求就來個快照
    if time.Since(start) > 300*time.Millisecond {
        // 這裡為了簡化只做一次,實際上你可以做多次
        once.Do(func() {
            // 擷取快照
            var b bytes.Buffer
            _, err = fr.WriteTo(&b)
            if err != nil {
                log.Print(err)
                return
            }
            // 把快照寫入檔案
            if err := os.WriteFile("trace.out", b.Bytes(), 0o755); err != nil {
                log.Print(err)
                return
            }
        })
    }
})
log.Fatal(http.ListenAndServe(":8080", nil))

追蹤讀取器 API

// 開始從標準輸入讀取追蹤資料。
r, err := trace.NewReader(os.Stdin)
if err != nil {
    log.Fatal(err)
}
var blocked int
var blockedOnNetwork int
for {
    // 讀取事件
    ev, err := r.ReadEvent()
    if err == io.EOF {
        break
    } else if err != nil {
        log.Fatal(err)
    }
    // 處理它
    if ev.Kind() == trace.EventStateTransition {
        st := ev.StateTransition()
        if st.Resource.Kind == trace.ResourceGoroutine {
            id := st.Resource.Goroutine()
            from, to := st.GoroutineTransition()
            // 查找阻塞的 goroutine 並統計
            if from.Executing() && to == trace.GoWaiting {
                blocked++
                if strings.Contains(st.Reason, "network") {
                    blockedOnNetwork++
                }
            }
        }
    }
}

// 印出所需數值
p := 100 * float64(blockedOnNetwork) / float64(blocked)
fmt.Printf("%2.3f%% instances of goroutines blocking were to block on the network\n", p)