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  }