github.com/mier85/go-sensor@v1.30.1-0.20220920111756-9bf41b3bc7e0/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/mier85/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 }