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

用戶端應用程式和前端網頁開發人員通常會使用 Android Studio CPU 分析器Chrome 內建的分析工具等工具,提升程式碼效能,但後端服務開發人員卻很少使用或採用同等技術。無論服務開發人員的程式碼是在 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 應用程式),請先建立帳戶。登入 Google Cloud Platform 主控台 (console.cloud.google.com),然後建立新專案:

Screenshot from 2016-02-10 12:45:26.png

請記住專案 ID,這是所有 Google Cloud 專案中不重複的名稱 (上述名稱已遭占用,因此不適用於您,抱歉!)。本程式碼研究室稍後會將其稱為 PROJECT_ID

接著,您必須在 Cloud 控制台中啟用帳單,才能使用 Google Cloud 資源。

完成本程式碼研究室的費用不應超過數美元,但如果您決定使用更多資源,或是將資源繼續執行 (請參閱本文件結尾的「清除」一節),則可能會增加費用。

Google Cloud Platform 新使用者享有價值 $300 美元的免費試用期

Google Cloud Shell

雖然可以透過筆電遠端操作 Google Cloud,但為了簡化本程式碼研究室的設定,我們將使用 Google Cloud Shell,這是可在雲端執行的指令列環境。

啟用 Google Cloud Shell

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

然後按一下「啟動 Cloud Shell」:

佈建並連線至環境的作業只需幾分鐘的時間:

這部虛擬機器搭載各種您需要的開發工具,提供永久的 5 GB 主目錄,而且在 Google Cloud 中運作,可大幅提升網路效能和驗證功能。您只需要瀏覽器或 Google Chromebook,就能完成這個實驗室的大部分工作 (甚至全部)。

連線至 Cloud Shell 後,您應會發現自己通過驗證,且專案已設為您的「PROJECT_ID」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 控制台中,按一下左側導覽列中的「Profiler」,前往 Profiler 使用者介面:

或者,您也可以使用 Cloud Console 搜尋列前往 Profiler UI:只要輸入「Stackdriver Profiler」,然後選取找到的項目即可。無論哪種方式,您都應該會看到「沒有可顯示的資料」訊息,如下所示。這個專案是新專案,因此尚未收集任何剖析資料。

現在該來剖析一些內容了!

我們將使用在 GitHub 上提供的簡易合成 Go 應用程式。在您仍開啟的 Cloud Shell 終端機中 (且當「沒有要顯示的資料」訊息仍顯示在 Profiler UI 中時),執行下列指令:

$ 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
...

收集到第一個設定檔後,使用者介面很快就會更新。之後系統不會自動更新,因此如要查看新資料,您必須手動重新整理分析器使用者介面。如要這麼做,請在時間間隔挑選器中按兩下「立即」按鈕:

UI 重新整理後,您會看到類似下方的內容:

剖析類型選取器會顯示五種可用的剖析類型:

接著,我們將逐一檢視各類設定檔和一些重要的 UI 功能,然後進行一些實驗。此時您不再需要 Cloud Shell 終端機,因此可以按下 Ctrl-C 並輸入「exit」來結束。

收集到一些資料後,我們來仔細看看。我們使用合成應用程式 (Github 上提供來源),模擬實際工作環境中各種效能問題的典型行為。

耗用大量 CPU 的程式碼

選取 CPU 設定檔類型。載入 UI 後,您會在火焰圖中看到 load 函式的四個葉節點,這些節點共同代表所有 CPU 消耗量:

這個函式專門用於執行緊密迴圈,耗用大量 CPU 週期:

main.go

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

函式是透過四個呼叫路徑,從 busyloop() 間接呼叫:busyloop → {foo1, foo2} → {bar, baz} → load。函式方塊的寬度代表特定呼叫路徑的相對成本。在這個例子中,四條路徑的費用大致相同。在實際程式中,您會想著重於最佳化對效能影響最大的呼叫路徑。火焰圖會以較大的方塊,從視覺上強調較昂貴的路徑,方便您找出這些路徑。

您可以使用設定檔資料篩選器,進一步調整顯示內容。舉例來說,請嘗試新增「顯示堆疊」篩選器,並將「baz」指定為篩選字串。您應該會看到類似下方的螢幕截圖,其中只顯示四個 load() 呼叫路徑中的兩個。這兩條路徑是唯一會經過名稱中含有「baz」字串的函式。如果您只想專注於大型節目的一部分 (例如您只擁有部分內容),這類篩選功能就非常實用。

耗用大量記憶體的程式碼

現在切換至「Heap」設定檔類型。請務必移除先前實驗中建立的篩選器。您現在應該會看到火焰圖,其中 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, 結尾的堆疊,因為這些通常是閒置的 goroutine,相較於等待 I/O 的 goroutine,較不值得關注。

執行緒剖析類型也有助於找出程式中執行緒等待其他程式部分擁有的互斥鎖時間過長的情況,但下列剖析類型更適合用於此用途。

競爭密集型程式碼

「爭用」設定檔類型會找出程式中最「需要」的鎖定。這個剖析資料類型適用於 Go 程式,但必須在代理程式設定程式碼中指定「MutexProfiling: true」,才能明確啟用。這項收集作業會記錄 (在「爭用」指標下),特定鎖定由 goroutine A 解除鎖定時,另一個 goroutine B 等待鎖定解除鎖定的次數。此外,系統也會記錄遭封鎖的 Goroutine 等待鎖定的時間 (在「延遲」指標下方)。在本例中,只有一個爭用堆疊,鎖定的總等待時間為 11.03 秒:

產生這個設定檔的程式碼包含 4 個爭奪互斥鎖的 Goroutine:

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!

瞭解詳情

授權

這項內容採用的授權為 Creative Commons 姓名標示 2.0 通用授權。