github.com/mier85/go-sensor@v1.30.1-0.20220920111756-9bf41b3bc7e0/autoprofile/internal/recorder.go (about) 1 // (c) Copyright IBM Corp. 2021 2 // (c) Copyright Instana Inc. 2020 3 4 package internal 5 6 import ( 7 "sync" 8 "time" 9 10 "github.com/mier85/go-sensor/autoprofile/internal/logger" 11 ) 12 13 // DefaultMaxBufferedProfiles is the default number of profiles to keep in recorder buffer 14 const DefaultMaxBufferedProfiles = 100 15 16 // SendProfilesFunc is a callback to emit collected profiles from recorder 17 type SendProfilesFunc func([]AgentProfile) error 18 19 // NoopSendProfiles is the default function to be called by Recorded to send collected profiles 20 func NoopSendProfiles([]AgentProfile) error { 21 logger.Warn( 22 "autoprofile.SendProfiles callback is not set, ", 23 "make sure that you have it configured using autoprofile.SetSendProfilesFunc() in your code", 24 ) 25 26 return nil 27 } 28 29 // Recorder collects and stores recorded profiles 30 type Recorder struct { 31 FlushInterval int64 32 MaxBufferedProfiles int 33 SendProfiles SendProfilesFunc 34 35 started Flag 36 flushTimer *Timer 37 queue []AgentProfile 38 queueLock *sync.Mutex 39 lastFlushTimestamp int64 40 backoffSeconds int64 41 } 42 43 // NewRecorder initializes and returns a new profile recorder 44 func NewRecorder() *Recorder { 45 mq := &Recorder{ 46 FlushInterval: 5, 47 SendProfiles: NoopSendProfiles, 48 MaxBufferedProfiles: DefaultMaxBufferedProfiles, 49 50 queue: make([]AgentProfile, 0), 51 queueLock: &sync.Mutex{}, 52 } 53 54 return mq 55 } 56 57 // Start initiates profile recorder flush loop 58 func (pr *Recorder) Start() { 59 if !pr.started.SetIfUnset() { 60 return 61 } 62 63 pr.flushTimer = NewTimer(0, time.Duration(pr.FlushInterval)*time.Second, func() { 64 pr.Flush() 65 }) 66 } 67 68 // Stop terminates profile recorder flush loop 69 func (pr *Recorder) Stop() { 70 if !pr.started.UnsetIfSet() { 71 return 72 } 73 74 if pr.flushTimer != nil { 75 pr.flushTimer.Stop() 76 } 77 } 78 79 // Size returns the number of profiles enqueued for submission 80 func (pr *Recorder) Size() int { 81 pr.queueLock.Lock() 82 defer pr.queueLock.Unlock() 83 84 return len(pr.queue) 85 } 86 87 // Record stores collected AgentProfile and enqueues it for submission 88 func (pr *Recorder) Record(record AgentProfile) { 89 if pr.MaxBufferedProfiles < 1 { 90 return 91 } 92 93 pr.queueLock.Lock() 94 pr.queue = append(pr.queue, record) 95 if len(pr.queue) > pr.MaxBufferedProfiles { 96 pr.queue = pr.queue[1:len(pr.queue)] 97 } 98 pr.queueLock.Unlock() 99 100 logger.Debug("Added record to the queue", record) 101 } 102 103 // Flush forces the recorder to submit collected profiles 104 func (pr *Recorder) Flush() { 105 if pr.Size() == 0 { 106 return 107 } 108 109 now := time.Now().Unix() 110 111 // flush only if backoff time is elapsed 112 if pr.lastFlushTimestamp+pr.backoffSeconds > now { 113 return 114 } 115 116 pr.queueLock.Lock() 117 outgoing := pr.queue 118 pr.queue = make([]AgentProfile, 0) 119 pr.queueLock.Unlock() 120 121 pr.lastFlushTimestamp = now 122 123 if err := pr.SendProfiles(outgoing); err == nil { 124 // reset backoff 125 pr.backoffSeconds = 0 126 } else { 127 // prepend outgoing records back to the queue 128 pr.queueLock.Lock() 129 pr.queue = append(outgoing, pr.queue...) 130 pr.queueLock.Unlock() 131 132 // increase backoff up to 1 minute 133 logger.Error("Failed sending profiles, backing off next sending") 134 if pr.backoffSeconds == 0 { 135 pr.backoffSeconds = 10 136 } else if pr.backoffSeconds*2 < 60 { 137 pr.backoffSeconds *= 2 138 } 139 140 logger.Error(err) 141 } 142 }