프로필 데이터 다운로드

이 문서에서는 로컬 시스템에 프로필 데이터를 다운로드하고 Go 애플리케이션을 사용해서 프로필 데이터를 프로그래매틱 방식으로 검색하는 방법을 설명합니다.

Google Cloud 콘솔을 사용하여 프로필 다운로드

Flame 그래프에 표시된 프로필을 다운로드하려면 다운로드 를 클릭합니다.

Profiler는 다운로드된 파일의 이름을 다음 규칙에 따라 지정합니다.

profiler_[SERVICE_NAME]_[PROFILE_TYPE]_[FROM_DATE]_[TO_DATE]_[ZONE]_[VERSION].pb.gz

이 표현식의 각 항목은 다음과 같은 의미를 갖습니다.

  • SERVICE_NAME은 선택한 서비스를 의미합니다.
  • PROFILE_TYPE은 선택한 프로필 유형을 의미합니다.
  • FROM_DATETO_DATE는 지정한 시간 범위를 의미합니다.
  • ZONE은 선택한 영역을 의미합니다.
  • VERSION은 선택한 버전을 의미합니다.

예: profiler_docdemo-service_HEAP_2018-04-22T20_25_31Z_2018-05-22T20_25_31Z_us-east1-c.pb.gz

프로그래매틱 방식으로 프로필 다운로드

프로필 데이터를 검색하려면 ListProfiles API 메서드를 사용합니다. 다음 샘플 Go 프로그램은 이 API 사용을 보여줍니다.

샘플 프로그램은 실행되는 디렉터리에 폴더를 만들고 숫자가 있는 pprof 파일 집합을 생성합니다. 각 파일에는 profile000042.pb.gz와 비슷한 이름 지정 규칙이 사용됩니다. 각 디렉터리에는 프로필 데이터와 메타데이터 파일이 포함됩니다. metadata.csv에는 다운로드된 파일의 정보가 포함됩니다.


// Sample export shows how ListProfiles API can be used to download
// existing pprof profiles for a given project from GCP.
package main

import (
	"bytes"
	"context"
	"encoding/csv"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"time"

	cloudprofiler "cloud.google.com/go/cloudprofiler/apiv2"
	pb "cloud.google.com/go/cloudprofiler/apiv2/cloudprofilerpb"
	"google.golang.org/api/iterator"
)

var project = flag.String("project", "", "GCP project ID from which profiles should be fetched")
var pageSize = flag.Int("page_size", 100, "Number of profiles fetched per page. Maximum 1000.")
var pageToken = flag.String("page_token", "", "PageToken from a previous ListProfiles call. If empty, the listing will start from the begnning. Invalid page tokens result in error.")
var maxProfiles = flag.Int("max_profiles", 1000, "Maximum number of profiles to fetch across all pages. If this is <= 0, will fetch all available profiles")

const ProfilesDownloadedSuccessfully = "Read max allowed profiles"

// This function reads profiles for a given project and stores them into locally created files.
// The profile metadata gets stored into a 'metdata.csv' file, while the individual pprof files
// are created per profile.
func downloadProfiles(ctx context.Context, w io.Writer, project, pageToken string, pageSize, maxProfiles int) error {
	client, err := cloudprofiler.NewExportClient(ctx)
	if err != nil {
		return err
	}
	defer client.Close()
	log.Printf("Attempting to fetch %v profiles with a pageSize of %v for %v\n", maxProfiles, pageSize, project)

	// Initial request for the ListProfiles API
	request := &pb.ListProfilesRequest{
		Parent:    fmt.Sprintf("projects/%s", project),
		PageSize:  int32(pageSize),
		PageToken: pageToken,
	}

	// create a folder for storing profiles & metadata
	profilesDirName := fmt.Sprintf("profiles_%v", time.Now().Unix())
	if err := os.Mkdir(profilesDirName, 0750); err != nil {
		log.Fatal(err)
	}
	// create a file for storing profile metadata
	metadata, err := os.Create(fmt.Sprintf("%s/metadata.csv", profilesDirName))
	if err != nil {
		return err
	}
	defer metadata.Close()

	writer := csv.NewWriter(metadata)
	defer writer.Flush()

	writer.Write([]string{"File", "Name", "ProfileType", "Target", "Duration", "Labels"})

	profileCount := 0
	// Keep calling ListProfiles API till all profile pages are fetched or max pages reached
	profilesIterator := client.ListProfiles(ctx, request)
	for {
		// Read individual profile - the client will automatically make API calls to fetch next pages
		profile, err := profilesIterator.Next()

		if err == iterator.Done {
			log.Println("Read all available profiles")
			break
		}
		if err != nil {
			return fmt.Errorf("error reading profile from response: %w", err)
		}
		profileCount++

		filename := fmt.Sprintf("%s/profile%06d.pb.gz", profilesDirName, profileCount)
		err = os.WriteFile(filename, profile.ProfileBytes, 0640)

		if err != nil {
			return fmt.Errorf("unable to write file %s: %w", filename, err)
		}
		fmt.Fprintf(w, "deployment target: %v\n", profile.Deployment.Labels)

		labelBytes, err := json.Marshal(profile.Labels)
		if err != nil {
			return err
		}

		err = writer.Write([]string{filename, profile.Name, profile.Deployment.Target, profile.Duration.String(), string(labelBytes)})
		if err != nil {
			return err
		}

		if maxProfiles > 0 && profileCount >= maxProfiles {
			fmt.Fprintf(w, "result: %v", ProfilesDownloadedSuccessfully)
			break
		}

		if profilesIterator.PageInfo().Remaining() == 0 {
			// This signifies that the client will make a new API call internally
			log.Printf("next page token: %v\n", profilesIterator.PageInfo().Token)
		}
	}
	return nil
}

func main() {
	flag.Parse()
	// validate project ID
	if *project == "" {
		log.Fatalf("No project ID provided, please provide the GCP project ID via '-project' flag")
	}
	var writer bytes.Buffer
	if err := downloadProfiles(context.Background(), &writer, *project, *pageToken, *pageSize, *maxProfiles); err != nil {
		log.Fatal(err)
	}
	log.Println("Finished reading all profiles")
}

샘플 프로그램은 다음 명령줄 인수를 사용합니다.

  • project: 프로필이 검색된 프로젝트입니다. 필수 항목입니다.
  • page_size: API 호출별로 검색된 최대 프로필 수입니다. page_size의 최댓값은 1,000입니다. 지정하지 않으면 이 필드가 100으로 설정됩니다.
  • page_token: 다운로드를 재개하기 위해 이전 프로그램 실행으로 생성된 문자열 토큰입니다. 선택사항.
  • max_profiles: 검색할 최대 프로필 수입니다. 양수가 아닌 정수가 제공된 경우 프로그램이 모든 프로필을 검색하도록 시도합니다.
    선택사항.

샘플 애플리케이션 실행

샘플 애플리케이션을 실행하려면 다음을 수행합니다.

  1. 저장소를 복제합니다.

    git clone https://github.com/GoogleCloudPlatform/golang-samples.git
    
  2. 샘플 프로그램이 포함된 디렉터리로 변경합니다.

    cd golang-samples/profiler/export
    
  3. YOUR_GCP_PROJECT를 Google Cloud 프로젝트의 ID로 바꾼 후 프로그램을 실행합니다.

    go run main.go -project YOUR_GCP_PROJECT -page_size 1000 -max_profiles 10000
    

프로그램을 완료하는 데 상당한 시간이 걸릴 수 있습니다. 프로그램이 현재 페이지를 검색한 후 다음 페이지에 대해 토큰을 출력합니다. 프로그램이 중단되면 토큰을 사용해서 프로세스를 재개할 수 있습니다.

다운로드한 프로필 보기

직렬화된 프로토콜 버퍼 형식으로 작성되어 다운로드된 파일을 읽으려면 오픈소스 pprof 도구를 사용합니다.