go 中更加強大的 traces

trace 的神奇之處在於輕鬆地揭祕程序,這是通過其他的方式是很難辦到的。例如,如果沒有執行採樣,在 CPU 的 profile 文件中很難看到大量 goroutines 因爲同一通道而阻塞的併發瓶頸。但在執行 trace 中,阻塞執行的情況會以驚人的清晰度顯示出來,阻塞的 goroutines 堆棧 traces 會迅速指出罪魁禍首。

Go 開發者甚至可以使用 tasks, regions, 和 logs 這些工具來觀測自己的程序,從而將高層關注點與低層執行細節關聯起來。

問題

遺憾的是,執行 trace 中的大量信息是被丟棄的。從歷史上看,有四大問題阻礙了執行 trace 。

如果你在過去幾年中有使用過 trace,那麼你很可能會因爲其中一個或多個問題而感到失望。但我們很高興地告訴大家,在過去的兩個 Go 版本中,我們在所有這四個方面都取得了很大的進步。

低成本 tracing

在 Go 1.21 之前,對於許多應用程序來說,trace 會佔用 CPU 的 10-20% 的開銷,這就限制了 trace 的使用場景,而不能像持續 CPU profileing 那樣使用。事實證明,trace 的大部分使用成本都來自於回溯。運行時產生的許多事件都附有 trace 的堆棧信息,這對於實際確定 goroutines 在執行過程中的關鍵時刻在做什麼非常有價值。

得益於 Felix Geisendörfer 和 Nick Ripley 在優化回溯效率方面所做的工作,執行 trace 的 CPU 運行時開銷已大幅降低,許多應用程序的 CPU 運行時開銷已降至 1-2%。你可以在 FelixFelix’s 的精彩博客中閱讀更多相關信息。

可擴展的 trace

trace 格式及其事件是圍繞相對高效的排放來進行設計的,但需要工具來解析和保持整個 trace 的狀態。分析一個幾百兆的 trace 可能需要幾個 G 的內存!

不幸的是,這個問題在生成 trace 的時候造成的。爲了降低運行時開銷,所有事件都被寫入線程本地的緩衝區。但這意味着事件的出現順序與真實順序不符,因此 trace 工具就有責任弄清到底發生了什麼。

要在保持較低開銷的同時擴大 trace 範圍,關鍵在於偶爾分割正在生成的 trace。每個分割點的行爲有點像一次同時禁用並重新啓用 trace。到目前爲止的所有 trace 數據將代表一個完整的、自成一體的 trace,而新的 trace 數據將無縫地從原來的位置開始。

可以想象,要解決這個問題,需要重新思考並重寫 trace 運行時的基礎工作。我們很高興地宣佈,這項工作已在 Go 1.22 中完成,並已普遍可用。重寫帶來了很多很好的改進,包括對 go tool trace 命令的一些改進。如果你想了解更多細節,請參閱設計文檔。

(備註:go tool trace 仍會將完整的 trace 加載到內存中,但對於 Go 1.22+ 程序生成的 trace 來說,取消這一限制並且現在是可用的)。

黑匣子

假設你正在處理一項網絡服務,而 RPC 耗時很長。你不能在知道 RPC 已經耗時很長時間時之後纔開始跟蹤,因爲導致請求緩慢的根本原因已經過去了,而且沒有被記錄下來。

有一種技術可以幫助解決這個問題,叫做黑匣子,你可能已經在其他編程環境中熟悉了這種技術。黑匣子的原理是持續 tracing,並始終保持最新的跟蹤數據,以防萬一。然後,一旦有有趣的事情發生,程序就可以直接寫出它所擁有的一切!

在 tracing 可以拆分之前,這幾乎是不可能實現的。但是,由於連續跟蹤的開銷較低,而且運行時可以隨時拆分跟蹤,因此現在可以直接實現黑匣子。

因此,我們很高興地宣佈將在 golang.org/x/exp/trace package 軟件包中提供實驗性的黑匣子記錄器。

請試用一下!下面是一個設置黑匣子用來記錄長 HTTP 請求的示例,供你開始使用。

    fr := trace.NewFlightRecorder()
    fr.Start()
    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))

如果你有任何反饋意見,無論是正面的還是負面的,proposal issue 63185 都請與我們分享!

trace reader API

在重寫 trace 實現的同時,我們還努力重構其他 trace 內部結構,如 go tool trace。因此,我們嘗試創建一個足以共享的 trace reader API,使 trace 更方便使用。

就像黑匣子一樣,我們很高興地宣佈,我們也有一個實驗性的 trace reader 的 API,希望與大家分享。它和黑匣子在同一個軟件包中。

我們認爲在此基礎上進行開發已經足夠好了,所以請試用一下!下面是一個示例,用於檢測因爲等待網絡而阻塞的 goroutine 的比例。

    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()
                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)

就像黑匣子一樣,有一個問題的提案 也是留下反饋意見的好地方!

Dominik Honnef 很早就試用了這個 API,並提供了很好的反饋,還爲舊版本的 trace API 提供了支持。

謝謝!

這項工作的完成,在很大程度上要歸功於診斷工作組成員 diagnostics working group 的幫助。診斷工作組在一年多之前成立,由 go 社區相關人員組成,並向公衆開放。

在此,我們要向去年定期參加診斷會議的社區成員:Felix Geisendörfer、Nick Ripley、Rhys Hiltner、Dominik Honnef、Bryan Boreham 和 thepudds 表示感謝。

你們的討論、反饋和付出對我們取得今天的成績起到了至關重要的作用。謝謝你們

引用

  1. gotraceui tool

  2. rethinking and rewriting a lot of the foundation of the trace implementation

  3. trace tasks

  4. trace regions

  5. trace logs

  6. felix’s great blog post

  7. rethinking and rewriting a lot of the foundation of the trace implementation

  8. A lot of nice improvements

  9. tracer design document

  10. 1.22 removing this limitation



本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Ujtv1j4ty-Jf_D6VJMf0Dw