使用 Stackdriver Profiler 分析實際工作環境效能

用戶端應用程式和前端網頁開發人員通常會使用 Android Studio CPU ProfilerChrome 提供的剖析工具來改善程式碼的效能,但對後端服務來說,同等技術並不明顯且易於採用。無論程式碼是在 Google Cloud Platform 還是其他地方執行,Stackdriver Profiler 也能將相同的功能提供給服務開發人員。

這個工具會收集來自實際工作環境應用程式的 CPU 使用量和記憶體配置資訊。進而將這些資訊歸給應用程式的原始碼,協助您找出應用程式使用最多資源的部分,並標明程式碼的效能特性。這項工具採用的收集技術較低,因此適合在實際工作環境中使用。

在本程式碼研究室中,您將瞭解如何為 Go 程式設定 Stackdriver Profiler,並熟悉這項工具對應用程式效能的深入分析資訊。

您將會瞭解的內容

  • 如何設定 Go 程式以使用 Stackdriver Profiler 剖析。
  • 如何使用 Stackdriver Profiler 收集、查看及分析成效資料。

軟硬體需求

  • Google Cloud Platform 專案
  • 瀏覽器,例如 ChromeFirefox
  • 熟悉標準 Linux 文字編輯器,例如 Vim、EMAC 或 Nano

您要如何使用本教學課程?

唯讀閱讀 閱讀內容並完成練習

您對於 Google Cloud Platform 的評價如何?

初級 中級 專業

自行調整環境設定

如果您還沒有 Google 帳戶 (Gmail 或 Google Apps),請先建立帳戶。登入 Google Cloud Platform 主控台 (console.cloud.google.com),然後建立新專案:

2016-02-10 12:45:26.png 的螢幕擷取畫面

提醒您,專案編號是所有 Google Cloud 專案的不重複名稱 (使用上述名稱後就無法使用,敬請見諒!)此程式碼研究室稍後將稱為 PROJECT_ID

接著,您必須在 Cloud Console 中啟用計費功能,才能使用 Google Cloud 資源。

完成這個程式碼研究室的成本應該不會超過新臺幣 $300 元,但如果您決定繼續使用更多資源,或是讓資源繼續運作 (請參閱本文件結尾的「清除設定」一節),就有可能需要更多成本。

新加入 Google Cloud Platform 的使用者可免費試用 $300 美元

Google Cloud Shell

雖然您可以將 Google Cloud 從筆記型電腦從遠端執行,但在本程式碼研究室中,我們仍將使用 Google Cloud Shell,這是一個在 Cloud 執行的指令列環境。

啟用 Google Cloud Shell

在 GCP 主控台中,按一下右上角的工具列的 Cloud Shell 圖示:

然後按一下 [Start Cloud Shell]。

佈建和連線至環境只需要幾分鐘的時間:

這部虛擬機器已載入所有您需要的開發工具。這項服務提供永久性的 5GB 主目錄,可在 Google Cloud 中運作,大幅提升網路效能和驗證效能。在這個研究室中,您可以透過瀏覽器或 Google Chromebook 完成大部分的工作。

連線至 Cloud Shell 之後,您應該會看到您已經通過驗證,且專案已經設定為 PROJECT_ID

在 Cloud Shell 中執行下列指令,確認您的身分已通過驗證:

gcloud auth list

指令輸出

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

指令輸出

[core]
project = <PROJECT_ID>

如果沒有,則可使用下列指令設定:

gcloud config set project <PROJECT_ID>

指令輸出

Updated property [core/project].

在 Cloud Console 中,按一下左側導覽列中的 [Profiler],前往 Profiler 使用者介面:

或者,您也可以使用 Cloud Console 搜尋列瀏覽至 Profiler 使用者介面:輸入「Stackdriver Profiler」,然後選取找到的項目。無論採用哪種方式,您都應會看到「沒有資料」訊息的 Profiler 使用者介面。專案才剛建立,因此尚未收集任何剖析資料。

現在該跟大家分享一些個人資料了!

我們會使用在 GitHub 上提供的簡單合成 Go 應用程式。在仍在開啟的 Cloud Shell 終端機中 (而「沒有資料可以顯示」訊息則會顯示在 Profiler 使用者介面中),然後執行下列指令:

$ go get -u github.com/GoogleCloudPlatform/golang-samples/profiler/...

然後切換到應用程式目錄:

$ cd ~/gopath/src/github.com/GoogleCloudPlatform/golang-samples/profiler/hotapp

目錄內含「main.go」檔案,也就是啟用剖析代理程式的合成應用程式:

main.go

...
import (
        ...
        "cloud.google.com/go/profiler"
)
...
func main() {
        err := profiler.Start(profiler.Config{
                Service:        "hotapp-service",
                DebugLogging:   true,
                MutexProfiling: true,
        })
        if err != nil {
                log.Fatalf("failed to start the profiler: %v", err)
        }
        ...
}

根據預設,剖析代理程式會收集 CPU、堆積和執行緒設定檔。這裡的程式碼可收集多方 (又稱「內容」) 設定檔。

現在,執行計劃:

$ go run main.go

在程式執行期間,剖析代理程式會定期收集五種設定類型的設定檔。這些集合會隨時間隨機調整 (每個類型各設定檔的平均速率為每分鐘一次),因此收集各類型資料最多可能需要三分鐘的時間。計劃會在建立設定檔時通知您。上述設定中的 DebugLogging 標記會啟用訊息;否則,代理程式會自動靜音執行:

$ go run main.go
2018/03/28 15:10:24 profiler has started
2018/03/28 15:10:57 successfully created profile THREADS
2018/03/28 15:10:57 start uploading profile
2018/03/28 15:11:19 successfully created profile CONTENTION
2018/03/28 15:11:30 start uploading profile
2018/03/28 15:11:40 successfully created profile CPU
2018/03/28 15:11:51 start uploading profile
2018/03/28 15:11:53 successfully created profile CONTENTION
2018/03/28 15:12:03 start uploading profile
2018/03/28 15:12:04 successfully created profile HEAP
2018/03/28 15:12:04 start uploading profile
2018/03/28 15:12:04 successfully created profile THREADS
2018/03/28 15:12:04 start uploading profile
2018/03/28 15:12:25 successfully created profile HEAP
2018/03/28 15:12:25 start uploading profile
2018/03/28 15:12:37 successfully created profile CPU
...

收集設定檔之後,使用者介面會立即更新資料。此後,它就不會再自動更新,因此如要查看新的資料,您必須手動重新整理 Profiler 使用者介面。方法很簡單,請按一下時間間隔挑選器中的兩次 [現在] 按鈕:

重新整理使用者介面後,您會看到下列資訊:

設定檔類型選取器會顯示 5 種可用的設定檔類型:

現在,我們要檢查每種設定檔類型和一些重要的 UI 功能,然後進行一些實驗。在這個階段,您不再需要 Cloud Shell 終端機,只要按下 CTRL-C 並輸入「exit」,即可結束該終端機。

現在我們收集了一些資料,接著就來進一步探究資料。我們使用合成應用程式 (該來源可在 GitHub 上取得),模擬在實際工作環境中模擬各種類型效能問題的一般行為。

耗用大量 CPU 的程式碼

選取 CPU 設定檔類型。使用者介面載入後,您將會在火焰圖中看到 load 函式的四個分葉區塊,而這些 CPU 的記憶體用量皆會計入:

這個函式專門執行,透過執行循環迴圈來耗用大量 CPU 週期:

main.go

func load() {
        for i := 0; i < (1 << 20); i++ {
        }
}

此函式會透過四個呼叫路徑從 busyloop() 間接呼叫:busyloop → {foo1, foo2} → {bar, baz} → load。函式方塊的寬度代表特定呼叫路徑的相對成本。在此,這四個路徑的成本大致相同。實際上,您想著重在改善成效最重要的來電路徑。火焰圖以較顯眼的方式突顯出較昂貴的路徑,而大型方塊則更容易辨識。

設定檔資料篩選器可讓您進一步調整顯示方式。舉例來說,您可以嘗試加入「顯示堆疊」篩選器,指定「baz」做為篩選器字串。您應該會看到類似下方的螢幕截圖,其中只會顯示在 load() 的四個呼叫路徑中。這兩個路徑是唯一經過「&bt」字串之函式的路徑名稱。當您想要專心處理較大型的計畫時 (例如您只擁有一部分),這種篩選功能就非常實用。

耗用大量記憶體的程式碼

接著切換為「堆積」設定檔類型。請務必移除先前實驗建立的所有篩選器。系統現在會顯示火焰圖,其中 allocImpl 的名稱為 alloc,會顯示為應用程式記憶體的主要消費者:

火焰圖上方的摘要表格指出該應用程式使用的記憶體總量平均約為 57.4 MiB,其中大部分為 allocImpl 函式的分配方式。不過,由於執行這個函式的緣故:

main.go

func allocImpl() {
        // Allocate 64 MiB in 64 KiB chunks
        for i := 0; i < 64*16; i++ {
                mem = append(mem, make([]byte, 64*1024))
        }
}

這個函式會執行一次,將 64 MiB 分配給較小的區塊,然後將指標指向全域區塊,以避免受到垃圾收集。請注意,分析器使用的記憶體容量與 64 MiB 略有不同:Go 堆積分析器是統計工具,因此測量結果為低負載,卻不是位元組的準確性。看到這樣的差異約為 10% 時,不要感到驚訝。

內含大量 IO 程式碼

如果你選擇「執行緒」在設定檔類型選擇器中,顯示畫面會切換到火焰圖,其中大部分的寬度是由 waitwaitImpl 函式擷取。

在上方的火焰圖上方,您可以看到有 100 個 goroutine 透過 wait 函式呼叫了呼叫堆疊。這很準確,因為啟動這些等待的程式碼會如下所示:

main.go

func main() {
        ...
        // Simulate some waiting goroutines.
        for i := 0; i < 100; i++ {
                go wait()
        }

這種設定檔類型有助於瞭解程式是否停留在任何非預期的等待時間 (例如 I/O)。CPU 分析器通常不會對這類呼叫堆疊進行取樣,因為這不會佔用 CPU 的時間。您通常會想要使用「隱藏堆疊」篩選器,例如使用「執行緒」設定檔來篩選所有結尾為 gopark, 的堆疊,因為這些堆疊通常是閒置的大三地類,且相較於 I/O 大會上的堆疊。

會話串的個人資料類型有助於識別在學程計畫中其他部分的協作者,如果轉換期間有其他共同所擁有的共同連結時,這種做法也可以派上用場。以下的設定檔類型則非常實用。

高度消耗的程式碼

「內容設定檔」類型可用來識別「計畫」中最需要的鎖定功能。這個設定檔類型適用於 Go 程式,但必須在代理程式設定程式碼中指定「MutexProfiling: true」以明確啟用。這個集合的運作原理是記錄在「內容」指標下,當有特定鎖定時,由 goroutine A 解鎖,然後再有另一個 goroutine B 等候鎖定解鎖的次數。它也會記錄 (「延遲」指標) 中遭封鎖的等待服務等待鎖定的時間。在本範例中,有一個內容堆疊,且鎖定總等待時間為 11.03 秒:

產生這個設定檔的程式碼是由 4 個哥德語大戰相互結合:

main.go

func contention(d time.Duration) {
        contentionImpl(d)
}

func contentionImpl(d time.Duration) {
        for {
                mu.Lock()
                time.Sleep(d)
                mu.Unlock()
        }
}
...
func main() {
        ...
        for i := 0; i < 4; i++ {
                go contention(time.Duration(i) * 50 * time.Millisecond)
        }
}

在這個研究室中,您已瞭解如何將 Go 程式設定為與 Stackdriver Profiler 搭配使用。此外,你也學會如何使用這項工具收集、查看及分析成效資料。您現在可以將新技能運用至您在 Google Cloud Platform 上執行的實際服務。

您已經學習如何設定及使用 Stackdriver Profiler!

瞭解詳情

授權

本作品採用創用 CC 姓名標示 2.0 一般授權。