הורדת נתוני הפרופיל

במאמר הזה מוסבר איך להוריד את נתוני הפרופיל למערכת המקומית, ואיך לאחזר את נתוני הפרופיל באופן פרוגרמטי באמצעות אפליקציית Go.

הורדת פרופילים באמצעות מסוף Google Cloud

כדי להוריד את הפרופיל שמוצג בתרשים הלהבות, לוחצים על הורדה .

כלי הפרופיל משתמש במוסכמת השמות הבאה לקובץ שהורד:

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

בביטוי הזה:

  • SERVICE_NAME מכיל את בחירת השירות
  • PROFILE_TYPE מכיל את הבחירה שלכם בסוג הפרופיל
  • השדות FROM_DATE ו-TO_DATE מכילים את הגדרות טווח הזמן
  • ZONE מכיל את הבחירה שלכם באזור
  • VERSION מכיל את הבחירה שלכם בגרסה

לדוגמה: profiler_docdemo-service_HEAP_2018-04-22T20_25_31Z_2018-05-22T20_25_31Z_us-east1-c.pb.gz

הורדת פרופילים באופן פרוגרמטי

כדי לאחזר נתוני פרופיל, משתמשים בשיטת ה-API ‏ListProfiles. בדוגמה הבאה של תוכנית 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 הפרויקט:

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

יכול להיות שיעבור זמן רב עד שהתוכנית תסתיים. התוכנית מוציאה טוקן לדף הבא אחרי אחזור הדף הנוכחי. אם התוכנית נקטעת, אפשר להשתמש באסימון כדי להמשיך את התהליך.

צפייה בפרופילים שהורדו

כדי לקרוא קובץ שהורדתם, שנכתב בפורמט מאגר אחסון לפרוטוקולים שעבר סריאליזציה, אתם יכולים להשתמש בכלי pprof בקוד פתוח.