github.com/waldiirawan/apm-agent-go/v2@v2.2.2/profiling.go (about)

     1  // Licensed to Elasticsearch B.V. under one or more contributor
     2  // license agreements. See the NOTICE file distributed with
     3  // this work for additional information regarding copyright
     4  // ownership. Elasticsearch B.V. licenses this file to you under
     5  // the Apache License, Version 2.0 (the "License"); you may
     6  // not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing,
    12  // software distributed under the License is distributed on an
    13  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    14  // KIND, either express or implied.  See the License for the
    15  // specific language governing permissions and limitations
    16  // under the License.
    17  
    18  package apm // import "github.com/waldiirawan/apm-agent-go/v2"
    19  
    20  import (
    21  	"bytes"
    22  	"context"
    23  	"io"
    24  	"runtime/pprof"
    25  	"time"
    26  
    27  	"github.com/pkg/errors"
    28  )
    29  
    30  type profilingState struct {
    31  	profileType  string
    32  	profileStart func(io.Writer) error
    33  	profileStop  func()
    34  	sender       profileSender
    35  
    36  	interval time.Duration
    37  	duration time.Duration // not relevant to all profiles
    38  
    39  	timer      *time.Timer
    40  	timerStart time.Time
    41  	buf        bytes.Buffer
    42  	finished   chan struct{}
    43  }
    44  
    45  // newCPUProfilingState calls newProfilingState with the
    46  // profiler type set to "cpu", and using pprof.StartCPUProfile
    47  // and pprof.StopCPUProfile.
    48  func newCPUProfilingState(sender profileSender) *profilingState {
    49  	return newProfilingState("cpu", pprof.StartCPUProfile, pprof.StopCPUProfile, sender)
    50  }
    51  
    52  // newHeapProfilingState calls newProfilingState with the
    53  // profiler type set to "heap", and using pprof.Lookup("heap").WriteTo(writer, 0).
    54  func newHeapProfilingState(sender profileSender) *profilingState {
    55  	return newLookupProfilingState("heap", sender)
    56  }
    57  
    58  func newLookupProfilingState(name string, sender profileSender) *profilingState {
    59  	profileStart := func(w io.Writer) error {
    60  		profile := pprof.Lookup(name)
    61  		if profile == nil {
    62  			return errors.Errorf("no profile called %q", name)
    63  		}
    64  		return profile.WriteTo(w, 0)
    65  	}
    66  	return newProfilingState("heap", profileStart, func() {}, sender)
    67  }
    68  
    69  // newProfilingState returns a new profilingState,
    70  // with its timer stopped. The timer may be started
    71  // by calling profilingState.updateConfig.
    72  func newProfilingState(
    73  	profileType string,
    74  	profileStart func(io.Writer) error,
    75  	profileStop func(),
    76  	sender profileSender,
    77  ) *profilingState {
    78  	state := &profilingState{
    79  		profileType:  profileType,
    80  		profileStart: profileStart,
    81  		profileStop:  profileStop,
    82  		sender:       sender,
    83  		timer:        time.NewTimer(0),
    84  		finished:     make(chan struct{}, 1),
    85  	}
    86  	if !state.timer.Stop() {
    87  		<-state.timer.C
    88  	}
    89  	return state
    90  }
    91  
    92  func (state *profilingState) updateConfig(interval, duration time.Duration) {
    93  	if state.sender == nil {
    94  		// No profile sender, no point in starting a timer.
    95  		return
    96  	}
    97  	state.duration = duration
    98  	if state.interval == interval {
    99  		return
   100  	}
   101  	if state.timerStart.IsZero() {
   102  		state.interval = interval
   103  		state.resetTimer()
   104  	}
   105  	// TODO(axw) handle extending/cutting short running timers once
   106  	// it is possible to dynamically control profiling configuration.
   107  }
   108  
   109  func (state *profilingState) resetTimer() {
   110  	if state.interval != 0 {
   111  		state.timer.Reset(state.interval)
   112  		state.timerStart = time.Now()
   113  	} else {
   114  		state.timerStart = time.Time{}
   115  	}
   116  }
   117  
   118  // start spawns a goroutine that will capture a profile, send it using state.sender,
   119  // and finally signal state.finished.
   120  //
   121  // start will return immediately after spawning the goroutine.
   122  func (state *profilingState) start(ctx context.Context, logger Logger, metadata io.Reader) {
   123  	// The state.duration field may be updated after the goroutine starts,
   124  	// by the caller, so it must be read outside the goroutine.
   125  	duration := state.duration
   126  	go func() {
   127  		defer func() { state.finished <- struct{}{} }()
   128  		if err := state.profile(ctx, duration); err != nil {
   129  			if logger != nil && ctx.Err() == nil {
   130  				logger.Errorf("%s", err)
   131  			}
   132  			return
   133  		}
   134  		// TODO(axw) backoff like SendStream requests
   135  		if err := state.sender.SendProfile(ctx, metadata, &state.buf); err != nil {
   136  			if logger != nil && ctx.Err() == nil {
   137  				logger.Errorf("failed to send %s profile: %s", state.profileType, err)
   138  			}
   139  			return
   140  		}
   141  		if logger != nil {
   142  			logger.Debugf("sent %s profile", state.profileType)
   143  		}
   144  	}()
   145  }
   146  
   147  func (state *profilingState) profile(ctx context.Context, duration time.Duration) error {
   148  	state.buf.Reset()
   149  	if err := state.profileStart(&state.buf); err != nil {
   150  		return errors.Wrapf(err, "failed to start %s profile", state.profileType)
   151  	}
   152  	defer state.profileStop()
   153  
   154  	if duration > 0 {
   155  		timer := time.NewTimer(duration)
   156  		defer timer.Stop()
   157  		select {
   158  		case <-ctx.Done():
   159  			return ctx.Err()
   160  		case <-timer.C:
   161  		}
   162  	}
   163  	return nil
   164  }
   165  
   166  type profileSender interface {
   167  	SendProfile(ctx context.Context, metadata io.Reader, profile ...io.Reader) error
   168  }