github.com/instana/go-sensor@v1.62.2-0.20240520081010-4919868049e1/autoprofile/internal/sampler_scheduler.go (about)

     1  // (c) Copyright IBM Corp. 2021
     2  // (c) Copyright Instana Inc. 2020
     3  
     4  package internal
     5  
     6  import (
     7  	"math/rand"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/instana/go-sensor/autoprofile/internal/logger"
    12  )
    13  
    14  var samplerActive Flag
    15  
    16  // SamplerConfig holds profile sampler setting
    17  type SamplerConfig struct {
    18  	LogPrefix          string
    19  	ReportOnly         bool
    20  	MaxProfileDuration int64
    21  	MaxSpanDuration    int64
    22  	MaxSpanCount       int32
    23  	SamplingInterval   int64
    24  	ReportInterval     int64
    25  }
    26  
    27  // Sampler gathers continuous profile samples over a period of time
    28  type Sampler interface {
    29  	Profile(duration int64, timespan int64) (*Profile, error)
    30  	Start() error
    31  	Stop() error
    32  	Reset()
    33  }
    34  
    35  // SamplerScheduler periodically runs the sampler for a time period
    36  type SamplerScheduler struct {
    37  	profileRecorder  *Recorder
    38  	sampler          Sampler
    39  	config           SamplerConfig
    40  	started          Flag
    41  	samplerTimer     *Timer
    42  	reportTimer      *Timer
    43  	profileLock      *sync.Mutex
    44  	profileStart     int64
    45  	samplingDuration int64
    46  	samplerStart     int64
    47  	samplerTimeout   *Timer
    48  }
    49  
    50  // NewSamplerScheduler initializes a new SamplerScheduler for a sampler
    51  func NewSamplerScheduler(profileRecorder *Recorder, samp Sampler, config SamplerConfig) *SamplerScheduler {
    52  	pr := &SamplerScheduler{
    53  		profileRecorder: profileRecorder,
    54  		sampler:         samp,
    55  		config:          config,
    56  		profileLock:     &sync.Mutex{},
    57  	}
    58  
    59  	return pr
    60  }
    61  
    62  // Start runs the SampleScheduler
    63  func (ss *SamplerScheduler) Start() {
    64  	if !ss.started.SetIfUnset() {
    65  		return
    66  	}
    67  
    68  	ss.profileLock.Lock()
    69  	defer ss.profileLock.Unlock()
    70  
    71  	ss.Reset()
    72  
    73  	if !ss.config.ReportOnly {
    74  		ss.samplerTimer = NewTimer(0, time.Duration(ss.config.SamplingInterval)*time.Second, func() {
    75  			time.Sleep(time.Duration(rand.Int63n(ss.config.SamplingInterval-ss.config.MaxSpanDuration)) * time.Second)
    76  			ss.startProfiling()
    77  		})
    78  	}
    79  
    80  	ss.reportTimer = NewTimer(0, time.Duration(ss.config.ReportInterval)*time.Second, func() {
    81  		ss.Report()
    82  	})
    83  }
    84  
    85  // Stop prevents the SamplerScheduler from running the sampler
    86  func (ss *SamplerScheduler) Stop() {
    87  	if !ss.started.UnsetIfSet() {
    88  		return
    89  	}
    90  
    91  	if ss.samplerTimer != nil {
    92  		ss.samplerTimer.Stop()
    93  	}
    94  
    95  	if ss.reportTimer != nil {
    96  		ss.reportTimer.Stop()
    97  	}
    98  }
    99  
   100  // Reset resets the sampler and clears the internal state of the scheduler
   101  func (ss *SamplerScheduler) Reset() {
   102  	ss.sampler.Reset()
   103  	ss.profileStart = time.Now().Unix()
   104  	ss.samplingDuration = 0
   105  }
   106  
   107  // Report retrieves the collected profile from the sampler and enqueues it for submission
   108  func (ss *SamplerScheduler) Report() {
   109  	if !ss.started.IsSet() {
   110  		return
   111  	}
   112  
   113  	profileTimespan := time.Now().Unix() - ss.profileStart
   114  
   115  	ss.profileLock.Lock()
   116  	defer ss.profileLock.Unlock()
   117  
   118  	if !ss.config.ReportOnly && ss.samplingDuration == 0 {
   119  		return
   120  	}
   121  
   122  	logger.Debug(ss.config.LogPrefix, "recording profile")
   123  
   124  	profile, err := ss.sampler.Profile(ss.samplingDuration, profileTimespan)
   125  	if err != nil {
   126  		logger.Error(err)
   127  		return
   128  	}
   129  
   130  	if len(profile.Roots) == 0 {
   131  		logger.Debug(ss.config.LogPrefix, "not recording empty profile")
   132  		ss.Reset()
   133  		return
   134  	}
   135  
   136  	ss.profileRecorder.Record(NewAgentProfile(profile))
   137  	logger.Debug(ss.config.LogPrefix, "recorded profile")
   138  
   139  	ss.Reset()
   140  }
   141  
   142  func (ss *SamplerScheduler) startProfiling() bool {
   143  	if !ss.started.IsSet() {
   144  		return false
   145  	}
   146  
   147  	ss.profileLock.Lock()
   148  	defer ss.profileLock.Unlock()
   149  
   150  	if ss.samplingDuration > ss.config.MaxProfileDuration*1e9 {
   151  		logger.Debug(ss.config.LogPrefix, "max sampling duration reached")
   152  		return false
   153  	}
   154  
   155  	if !samplerActive.SetIfUnset() {
   156  		return false
   157  	}
   158  
   159  	logger.Debug(ss.config.LogPrefix, "starting")
   160  
   161  	err := ss.sampler.Start()
   162  	if err != nil {
   163  		samplerActive.Unset()
   164  		logger.Error(err)
   165  		return false
   166  	}
   167  	ss.samplerStart = time.Now().UnixNano()
   168  	ss.samplerTimeout = NewTimer(time.Duration(ss.config.MaxSpanDuration)*time.Second, 0, func() {
   169  		ss.stopSampler()
   170  		samplerActive.Unset()
   171  	})
   172  
   173  	return true
   174  }
   175  
   176  func (ss *SamplerScheduler) stopSampler() {
   177  	ss.profileLock.Lock()
   178  	defer ss.profileLock.Unlock()
   179  
   180  	if ss.samplerTimeout != nil {
   181  		ss.samplerTimeout.Stop()
   182  	}
   183  
   184  	err := ss.sampler.Stop()
   185  	if err != nil {
   186  		logger.Error(err)
   187  		return
   188  	}
   189  	logger.Debug(ss.config.LogPrefix, "stopped")
   190  
   191  	ss.samplingDuration += time.Now().UnixNano() - ss.samplerStart
   192  }