DEV Community

Cover image for 使用google cloud profiler來對go gRPC server做效能分析
Bill Chung
Bill Chung

Posted on

3

使用google cloud profiler來對go gRPC server做效能分析

English version

About

這篇文章主要是整合 Cloud Profiler 至go的程式 (以gRPC Server為範例)

完整的範例在github

Profiler

Profiling是一種軟體效能分析的方法,而Profiler則是做Profiling的工具。簡單的說它就是測量軟體所花費的時間、空間、或其他的資源,近年來最常用的視覺化工具為FlameGraph, 其概念可以參考Brendan Gregg的網站google cloud

Cloud Profiler

Google出個一個服務叫Cloud Profiler,它可以讓你非常容易的自動取得並視覺化你的程式,只需要加進一小段簡單的程式碼,以下是一些關於Cloud Profiler的基本資訊:

  • 支援的程式語言: Python, Go, NodeJS, and Java
  • 價格: 免費
  • 資料保留時間: 30天
  • 對於程式的效能影響: 請參考這邊
  • 就算你的程式不是跑在GCP上,你還是可以使用,以下的範例就是只跑在本機,然後資料會傳送到Cloud Profiler做視覺化

gRPC server

因為這邊的範例使用gRPC server,所以在介紹整合前先簡單介紹一下該example gRPC service,如果你已經熟悉gRPC service,可以跳過此節

profobuf - Ping service

該服務只定義了一個Ping服務,位於proto資料夾,當中ping.proto:

syntax = "proto3";

package ping;
option go_package = ".;ping";

service Ping {
    // Get returns a response with same message id and body, and with timestamp.
    rpc Get (PingRequest) returns (PingResponse);
    // GetAfter is same as Ping but return the response after certain time
    rpc GetAfter (PingRequestWithSleep) returns (PingResponse);
    // GetRandom generates random strings and return, also produce lots of useless stuff to show the effects of heap
    rpc GetRandom (PingRequest) returns (PingResponse);
}


message PingRequest {
    string message_ID = 1;
    string message_body = 2;
}

message PingRequestWithSleep {
    string message_ID = 1;
    string message_body = 2;
    int32 sleep = 3;
}

message PingResponse {
    string message_ID = 1;
    string message_body = 2;
    uint64 timestamp = 3;
}
Enter fullscreen mode Exit fullscreen mode

如果你對於protobuf不熟悉,可以參考此文件

這個Ping服務有3個RPC (remote procedure call):

  1. Get: 單純回傳相同的訊息ID及body, 並帶server的timestamp
  2. GetAfter: 與Get相同單純回傳,但會等待給定的秒數後再回傳。
  3. GetRandom: 與Get相似,但body會變成一個隨機的文字,而在下面的實踐上回傳前會故意多做一些事,用意是產生一些cpu及memory用量,作為demo.

產生gRPC go code

  1. 需要安裝protoc - 請參考here
  2. 在你的terminal執行以下指令來產生go gRPC server/client codes (會產生一個檔案叫ping.pb.go):
make proto-go
Enter fullscreen mode Exit fullscreen mode

該repository已經包含該ping.pb.go檔案,但我使用的版本為
protoc-gen-go v1.25.0 & protoc v3.6.1,你可能會有不同的版本,所以重新產生以免不相同

Ping服務實現

以下是該服務的實踐,該檔案位於ping資夾當中的ping.go:

package Ping

import (
    "context"
    pb "github.com/billcchung/example-service/protobuf"
    "math/rand"
    "time"
)

var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

type Server struct{}

func (s Server) Get(ctx context.Context, req *pb.PingRequest) (res *pb.PingResponse, err error) {
    res = &pb.PingResponse{
        Message_ID:  req.Message_ID,
        MessageBody: req.MessageBody,
        Timestamp:   uint64(time.Now().UnixNano() / int64(time.Millisecond)),
    }
    return
}

func (s Server) GetAfter(ctx context.Context, req *pb.PingRequestWithSleep) (res *pb.PingResponse, err error) {
    time.Sleep(time.Duration(req.Sleep) * time.Second)
    return s.Get(ctx, &pb.PingRequest{Message_ID: req.Message_ID, MessageBody: req.MessageBody})
}

func (s Server) GetRandom(ctx context.Context, req *pb.PingRequest) (res *pb.PingResponse, err error) {
    var garbage []string
    for i := 0; i <= 1000000; i++ {
        garbage = append(garbage, string(letterRunes[rand.Intn(len(letterRunes))]))
    }
    return s.Get(ctx, &pb.PingRequest{Message_ID: req.Message_ID, MessageBody: string(letterRunes[rand.Intn(len(letterRunes))])})
}
Enter fullscreen mode Exit fullscreen mode

設定Cloud Profiler

前面提到只需要加入很簡單的程式碼就可以為你的服務做profiling,你只需要import "cloud.google.com/go/profiler"並在你程式啟動的同時啟動profiler即可, 該profiler是一個go routine在背景定時取樣並上傳至cloud profiler

以我們的範列,其程式如下(於main.go):

profiler.Start(profiler.Config{
    Service:   service,
    ServiceVersion: serviceVersion
    ProjectID: projectID,
})
Enter fullscreen mode Exit fullscreen mode

其中

  • Service為服務名稱,
  • ServiceVersion服務的版本
  • ProjectID為GCP的project ID

啟動Server服務

在啟動服務之前,請確保你的Cloud Profiler是已經開啟(enabled)的狀態,你可以從這邊開啟

另外因為這個example service並不會跑在GCP裡面,你需要有相對應的cloud profiler權限並確定已經登入glcoud, 若你尚未登入或需要權限請見這邊,

然後你就可以啟動服務(請將$PROJECT_ID換成你的GCP project ID):

go run main.go -p $PROJECT_ID
Enter fullscreen mode Exit fullscreen mode

執行client

一旦server開始執行,你可以執行在tools裡面的client端,該程式會依序call Get, GetAfter, 及 GetRandom,你會需要執行多次來確保profiler可以取得樣本:

for i in $(seq 1 1000); do go run tools/connect.go ; done
Enter fullscreen mode Exit fullscreen mode

檢視圖表及理解

待client執行一段時間後 (不需要等所有的loop跑完),你可以在cloud profiler console看到該服務的profiles及其視覺化(flamegraph):

Screen Shot 2020-11-08 at 10.36.57

你所看到的圖可能會跟我的不太一樣,但以我的圖為例

  • 它取得16個profiles樣本,其中CPU時間為520ms780ms
  • Server.GetRandom花了不少時間,而該函式為我們為服務所寫的
  • 沒有(或極少)Server.Get是因為該函式執行時間非常快,也沒有(或極少)GetAfter 因為 time.Sleep並不佔用CPU時間。你可能會有該兩個函式,但應該極少,因為採樣的角度來說會採到該兩函式的機率太小,而通常我們會使用profiling的時候我們也只在意佔用最多時間或資料的函式。

你可以發現Server.GetRandom使用了164.38ms,點擊可以看詳細是什麼函式在佔用時間:
Screen Shot 2020-11-08 at 10.38.01
你可以看到growsliceIntn佔了大多數時間:
Screen Shot 2020-11-08 at 10.38.08
Screen Shot 2020-11-08 at 10.38.14
當然該兩個函式是go bulitin, 這只是為了demo使用,讓你可以解讀及了解什麼函式佔用了最多的時間,在實務上的程式則可以提供一些資訊來幫忙你做優化

另外在Profile type也可以選擇其他的種類,例如選Heap來看記憶體使用量:
Screen Shot 2020-11-08 at 10.38.29
Server.Random 使用了 13.24MiB

最後值得一提的是Profile type中,Threads在go裡面指的是go routines

其他的資訊可以參考這邊

總結

在這篇文章中簡單的demo了

  • 如何使用及解讀Cloud profiler
  • 如何建構gRPC server

希望對有所幫助,我選擇gRPC作為範例是為了預計之後一系列的文章以gRPC/microservices為主的後端系統的建置(而非使用REST/json),以及kubernetes + service mesh的使用, 所有的系統會以GCP或自建為主

AWS Q Developer image

Your AI Code Assistant

Automate your code reviews. Catch bugs before your coworkers. Fix security issues in your code. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Get started free in your IDE

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay