github.com/lulzWill/go-agent@v2.1.2+incompatible/internal/analytics_events.go (about)

     1  package internal
     2  
     3  import (
     4  	"bytes"
     5  	"container/heap"
     6  
     7  	"github.com/lulzWill/go-agent/internal/jsonx"
     8  )
     9  
    10  // eventStamp allows for uniform random sampling of events.  When an event is
    11  // created it is given an eventStamp.  Whenever an event pool is full and events
    12  // need to be dropped, the events with the lowest stamps are dropped.
    13  type eventStamp float32
    14  
    15  func eventStampCmp(a, b eventStamp) bool {
    16  	return a < b
    17  }
    18  
    19  type analyticsEvent struct {
    20  	stamp eventStamp
    21  	jsonWriter
    22  }
    23  
    24  type analyticsEventHeap []analyticsEvent
    25  
    26  type analyticsEvents struct {
    27  	numSeen        int
    28  	events         analyticsEventHeap
    29  	failedHarvests int
    30  }
    31  
    32  func (events *analyticsEvents) NumSeen() float64  { return float64(events.numSeen) }
    33  func (events *analyticsEvents) NumSaved() float64 { return float64(len(events.events)) }
    34  
    35  func (h analyticsEventHeap) Len() int           { return len(h) }
    36  func (h analyticsEventHeap) Less(i, j int) bool { return eventStampCmp(h[i].stamp, h[j].stamp) }
    37  func (h analyticsEventHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }
    38  
    39  // Push and Pop are unused: only heap.Init and heap.Fix are used.
    40  func (h analyticsEventHeap) Push(x interface{}) {}
    41  func (h analyticsEventHeap) Pop() interface{}   { return nil }
    42  
    43  func newAnalyticsEvents(max int) *analyticsEvents {
    44  	return &analyticsEvents{
    45  		numSeen:        0,
    46  		events:         make(analyticsEventHeap, 0, max),
    47  		failedHarvests: 0,
    48  	}
    49  }
    50  
    51  func (events *analyticsEvents) addEvent(e analyticsEvent) {
    52  	events.numSeen++
    53  
    54  	if len(events.events) < cap(events.events) {
    55  		events.events = append(events.events, e)
    56  		if len(events.events) == cap(events.events) {
    57  			// Delay heap initialization so that we can have
    58  			// deterministic ordering for integration tests (the max
    59  			// is not being reached).
    60  			heap.Init(events.events)
    61  		}
    62  		return
    63  	}
    64  
    65  	if eventStampCmp(e.stamp, events.events[0].stamp) {
    66  		return
    67  	}
    68  
    69  	events.events[0] = e
    70  	heap.Fix(events.events, 0)
    71  }
    72  
    73  func (events *analyticsEvents) mergeFailed(other *analyticsEvents) {
    74  	fails := other.failedHarvests + 1
    75  	if fails >= failedEventsAttemptsLimit {
    76  		return
    77  	}
    78  	events.failedHarvests = fails
    79  	events.Merge(other)
    80  }
    81  
    82  func (events *analyticsEvents) Merge(other *analyticsEvents) {
    83  	allSeen := events.numSeen + other.numSeen
    84  
    85  	for _, e := range other.events {
    86  		events.addEvent(e)
    87  	}
    88  	events.numSeen = allSeen
    89  }
    90  
    91  func (events *analyticsEvents) CollectorJSON(agentRunID string) ([]byte, error) {
    92  	if 0 == events.numSeen {
    93  		return nil, nil
    94  	}
    95  
    96  	estimate := 256 * len(events.events)
    97  	buf := bytes.NewBuffer(make([]byte, 0, estimate))
    98  
    99  	buf.WriteByte('[')
   100  	jsonx.AppendString(buf, agentRunID)
   101  	buf.WriteByte(',')
   102  	buf.WriteByte('{')
   103  	buf.WriteString(`"reservoir_size":`)
   104  	jsonx.AppendUint(buf, uint64(cap(events.events)))
   105  	buf.WriteByte(',')
   106  	buf.WriteString(`"events_seen":`)
   107  	jsonx.AppendUint(buf, uint64(events.numSeen))
   108  	buf.WriteByte('}')
   109  	buf.WriteByte(',')
   110  	buf.WriteByte('[')
   111  	for i, e := range events.events {
   112  		if i > 0 {
   113  			buf.WriteByte(',')
   114  		}
   115  		e.WriteJSON(buf)
   116  	}
   117  	buf.WriteByte(']')
   118  	buf.WriteByte(']')
   119  
   120  	return buf.Bytes(), nil
   121  
   122  }