Source File
profiler.go
Belonging Package
cloud.google.com/go/profiler
package profiler
import (
gcemd
gax
gtransport
pb
edpb
grpcmd
)
var (
config Config
startOnce allowUntilSuccess
getProjectID = gcemd.ProjectID
getInstanceName = gcemd.InstanceName
getZone = gcemd.Zone
startCPUProfile = pprof.StartCPUProfile
stopCPUProfile = pprof.StopCPUProfile
writeHeapProfile = pprof.WriteHeapProfile
sleep = gax.Sleep
dialGRPC = gtransport.DialPool
onGCE = gcemd.OnGCE
serviceRegexp = regexp.MustCompile(`^[a-z]([-a-z0-9_.]{0,253}[a-z0-9])?$`)
profilingDone chan bool
)
const (
apiAddress = "cloudprofiler.googleapis.com:443"
xGoogAPIMetadata = "x-goog-api-client"
zoneNameLabel = "zone"
versionLabel = "version"
languageLabel = "language"
instanceLabel = "instance"
scope = "https://www.googleapis.com/auth/monitoring.write"
maxBackoff = time.Hour
backoffMultiplier = 1.3 // Backoff envelope increases by this factor on each retry.
retryInfoMetadata = "google.rpc.retryinfo-bin"
)
func ( Config, ...option.ClientOption) error {
:= startOnce.do(func() error {
return start(, ...)
})
return
}
func ( Config, ...option.ClientOption) error {
if := initializeConfig(); != nil {
debugLog("failed to initialize config: %v", )
return
}
if config.MutexProfiling {
if mutexEnabled = enableMutexProfiling(); !mutexEnabled {
return fmt.Errorf("mutex profiling is not supported by %s, requires Go 1.8 or later", runtime.Version())
}
}
:= context.Background()
:= []option.ClientOption{
option.WithEndpoint(config.APIAddr),
option.WithScopes(scope),
option.WithUserAgent(fmt.Sprintf("gcloud-go-profiler/%s", version.Repo)),
}
if !config.EnableOCTelemetry {
= append(, option.WithTelemetryDisabled())
}
= append(, ...)
, := dialGRPC(, ...)
if != nil {
debugLog("failed to dial GRPC: %v", )
return
}
, := initializeAgent(pb.NewProfilerServiceClient())
if != nil {
debugLog("failed to start the profiling agent: %v", )
return
}
go pollProfilerService(withXGoogHeader(), )
return nil
}
func ( string, ...interface{}) {
if config.DebugLogging {
log.Printf(, ...)
}
}
type agent struct {
client pb.ProfilerServiceClient
deployment *pb.Deployment
profileLabels map[string]string
profileTypes []pb.ProfileType
}
func ( grpcmd.MD) (time.Duration, error) {
:= [retryInfoMetadata]
if len() <= 0 {
return 0, errors.New("no retry info")
}
var edpb.RetryInfo
if := proto.Unmarshal([]byte([0]), &); != nil {
return 0,
} else if , := ptypes.Duration(.RetryDelay); != nil {
return 0,
} else {
if < 0 {
return 0, errors.New("negative retry duration")
}
return , nil
}
}
type retryer struct {
backoff gax.Backoff
md grpcmd.MD
}
func ( *retryer) ( error) (time.Duration, bool) {
, := status.FromError()
if != nil && .Code() == codes.Aborted {
, := abortedBackoffDuration(.md)
if == nil {
return , true
}
debugLog("failed to get backoff duration: %v", )
}
return .backoff.Pause(), true
}
func ( *agent) ( context.Context) *pb.Profile {
:= pb.CreateProfileRequest{
Parent: "projects/" + .deployment.ProjectId,
Deployment: .deployment,
ProfileType: .profileTypes,
}
var *pb.Profile
:= grpcmd.New(map[string]string{})
gax.Invoke(, func( context.Context, gax.CallSettings) error {
debugLog("creating a new profile via profiler service")
var error
, = .client.CreateProfile(, &, grpc.Trailer(&))
if != nil {
debugLog("failed to create profile, will retry: %v", )
}
return
}, gax.WithRetry(func() gax.Retryer {
return &retryer{
backoff: gax.Backoff{
Initial: initialBackoff,
Max: maxBackoff,
Multiplier: backoffMultiplier,
},
md: ,
}
}))
debugLog("successfully created profile %v", .GetProfileType())
return
}
func ( *agent) ( context.Context, *pb.Profile) {
var bytes.Buffer
:= .GetProfileType()
:= false
for , := range .profileTypes {
if == {
= true
break
}
}
if ! {
debugLog("skipping collection of disabled profile type: %v", )
return
}
switch {
case pb.ProfileType_CPU:
, := ptypes.Duration(.Duration)
if != nil {
debugLog("failed to get profile duration for CPU profile: %v", )
return
}
if := startCPUProfile(&); != nil {
debugLog("failed to start CPU profile: %v", )
return
}
sleep(, )
stopCPUProfile()
case pb.ProfileType_HEAP:
if := heapProfile(&); != nil {
debugLog("failed to write heap profile: %v", )
return
}
case pb.ProfileType_HEAP_ALLOC:
, := ptypes.Duration(.Duration)
if != nil {
debugLog("failed to get profile duration for allocation profile: %v", )
return
}
if := deltaAllocProfile(, , config.AllocForceGC, &); != nil {
debugLog("failed to collect allocation profile: %v", )
return
}
case pb.ProfileType_THREADS:
if := pprof.Lookup("goroutine").WriteTo(&, 0); != nil {
debugLog("failed to collect goroutine profile: %v", )
return
}
case pb.ProfileType_CONTENTION:
, := ptypes.Duration(.Duration)
if != nil {
debugLog("failed to get profile duration: %v", )
return
}
if := deltaMutexProfile(, , &); != nil {
debugLog("failed to collect mutex profile: %v", )
return
}
default:
debugLog("unexpected profile type: %v", )
return
}
.ProfileBytes = .Bytes()
.Labels = .profileLabels
:= pb.UpdateProfileRequest{Profile: }
debugLog("start uploading profile")
if , := .client.UpdateProfile(, &); != nil {
debugLog("failed to upload profile: %v", )
}
}
func ( context.Context, time.Duration, *bytes.Buffer) error {
if !mutexEnabled {
return errors.New("mutex profiling is not enabled")
}
, := mutexProfile()
if != nil {
return
}
sleep(, )
, := mutexProfile()
if != nil {
return
}
.Scale(-1)
, = profile.Merge([]*profile.Profile{, })
if != nil {
return
}
return .Write()
}
func () (*profile.Profile, error) {
:= pprof.Lookup("mutex")
if == nil {
return nil, errors.New("mutex profiling is not supported")
}
var bytes.Buffer
if := .WriteTo(&, 0); != nil {
return nil,
}
return profile.Parse(&)
}
func ( context.Context, ...string) context.Context {
:= append([]string{"gl-go", version.Go(), "gccl", version.Repo}, ...)
= append(, "gax", gax.Version, "grpc", grpc.Version)
, := grpcmd.FromOutgoingContext()
= .Copy()
[xGoogAPIMetadata] = []string{gax.XGoogHeader(...)}
return grpcmd.NewOutgoingContext(, )
}
func ( pb.ProfilerServiceClient) (*agent, error) {
:= map[string]string{languageLabel: "go"}
if config.Zone != "" {
[zoneNameLabel] = config.Zone
}
if config.ServiceVersion != "" {
[versionLabel] = config.ServiceVersion
}
:= &pb.Deployment{
ProjectId: config.ProjectID,
Target: config.Service,
Labels: ,
}
:= map[string]string{}
if config.Instance != "" {
[instanceLabel] = config.Instance
}
var []pb.ProfileType
if !config.NoCPUProfiling {
= append(, pb.ProfileType_CPU)
}
if !config.NoHeapProfiling {
= append(, pb.ProfileType_HEAP)
}
if !config.NoGoroutineProfiling {
= append(, pb.ProfileType_THREADS)
}
if !config.NoAllocProfiling {
= append(, pb.ProfileType_HEAP_ALLOC)
}
if mutexEnabled {
= append(, pb.ProfileType_CONTENTION)
}
if len() == 0 {
return nil, fmt.Errorf("collection is not enabled for any profile types")
}
return &agent{
client: ,
deployment: ,
profileLabels: ,
profileTypes: ,
}, nil
}
func ( Config) error {
config =
if config.Service == "" {
for , := range []string{"GAE_SERVICE", "K_SERVICE"} {
if := os.Getenv(); != "" {
config.Service =
break
}
}
}
if config.Service == "" {
return errors.New("service name must be configured")
}
if !serviceRegexp.MatchString(config.Service) {
return fmt.Errorf("service name %q does not match regular expression %v", config.Service, serviceRegexp)
}
if config.ServiceVersion == "" {
for , := range []string{"GAE_VERSION", "K_REVISION"} {
if := os.Getenv(); != "" {
config.ServiceVersion =
break
}
}
}
config.ProjectID =
}
if onGCE() {
var error
if config.ProjectID == "" {
if config.ProjectID, = getProjectID(); != nil {
return fmt.Errorf("failed to get the project ID from Compute Engine metadata: %v", )
}
}
if config.Zone == "" {
if config.Zone, = getZone(); != nil {
return fmt.Errorf("failed to get zone from Compute Engine metadata: %v", )
}
}
if config.Instance == "" {
if config.Instance, = getInstanceName(); != nil {
if , := .(gcemd.NotDefinedError); ! {
return fmt.Errorf("failed to get instance name from Compute Engine metadata: %v", )
}
debugLog("failed to get instance name from Compute Engine metadata, will use empty name: %v", )
}
}
} else {
if config.ProjectID == "" {
return fmt.Errorf("project ID must be specified in the configuration if running outside of GCP")
}
}
if config.APIAddr == "" {
config.APIAddr = apiAddress
}
return nil
}
func ( context.Context, *agent) {
debugLog("Cloud Profiler Go Agent version: %s", version.Repo)
debugLog("profiler has started")
for := 0; config.numProfiles == 0 || < config.numProfiles; ++ {
:= .createProfile()
.profileAndUpload(, )
}
if profilingDone != nil {
profilingDone <- true
}
![]() |
The pages are generated with Golds v0.3.2-preview. (GOOS=darwin GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds. |