github.com/newrelic/go-agent@v3.26.0+incompatible/internal/analytics_events.go (about)

     1  // Copyright 2020 New Relic Corporation. All rights reserved.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package internal
     5  
     6  import (
     7  	"bytes"
     8  	"container/heap"
     9  
    10  	"github.com/newrelic/go-agent/internal/jsonx"
    11  )
    12  
    13  type analyticsEvent struct {
    14  	priority Priority
    15  	jsonWriter
    16  }
    17  
    18  type analyticsEventHeap []analyticsEvent
    19  
    20  type analyticsEvents struct {
    21  	numSeen        int
    22  	events         analyticsEventHeap
    23  	failedHarvests int
    24  }
    25  
    26  func (events *analyticsEvents) NumSeen() float64  { return float64(events.numSeen) }
    27  func (events *analyticsEvents) NumSaved() float64 { return float64(len(events.events)) }
    28  
    29  func (h analyticsEventHeap) Len() int           { return len(h) }
    30  func (h analyticsEventHeap) Less(i, j int) bool { return h[i].priority.isLowerPriority(h[j].priority) }
    31  func (h analyticsEventHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }
    32  
    33  // Push and Pop are unused: only heap.Init and heap.Fix are used.
    34  func (h analyticsEventHeap) Push(x interface{}) {}
    35  func (h analyticsEventHeap) Pop() interface{}   { return nil }
    36  
    37  func newAnalyticsEvents(max int) *analyticsEvents {
    38  	return &analyticsEvents{
    39  		numSeen:        0,
    40  		events:         make(analyticsEventHeap, 0, max),
    41  		failedHarvests: 0,
    42  	}
    43  }
    44  
    45  func (events *analyticsEvents) capacity() int {
    46  	return cap(events.events)
    47  }
    48  
    49  func (events *analyticsEvents) addEvent(e analyticsEvent) {
    50  	events.numSeen++
    51  
    52  	if events.capacity() == 0 {
    53  		// Configurable event harvest limits may be zero.
    54  		return
    55  	}
    56  
    57  	if len(events.events) < cap(events.events) {
    58  		events.events = append(events.events, e)
    59  		if len(events.events) == cap(events.events) {
    60  			// Delay heap initialization so that we can have
    61  			// deterministic ordering for integration tests (the max
    62  			// is not being reached).
    63  			heap.Init(events.events)
    64  		}
    65  		return
    66  	}
    67  
    68  	if e.priority.isLowerPriority((events.events)[0].priority) {
    69  		return
    70  	}
    71  
    72  	events.events[0] = e
    73  	heap.Fix(events.events, 0)
    74  }
    75  
    76  func (events *analyticsEvents) mergeFailed(other *analyticsEvents) {
    77  	fails := other.failedHarvests + 1
    78  	if fails >= failedEventsAttemptsLimit {
    79  		return
    80  	}
    81  	events.failedHarvests = fails
    82  	events.Merge(other)
    83  }
    84  
    85  func (events *analyticsEvents) Merge(other *analyticsEvents) {
    86  	allSeen := events.numSeen + other.numSeen
    87  
    88  	for _, e := range other.events {
    89  		events.addEvent(e)
    90  	}
    91  	events.numSeen = allSeen
    92  }
    93  
    94  func (events *analyticsEvents) CollectorJSON(agentRunID string) ([]byte, error) {
    95  	if 0 == len(events.events) {
    96  		return nil, nil
    97  	}
    98  
    99  	estimate := 256 * len(events.events)
   100  	buf := bytes.NewBuffer(make([]byte, 0, estimate))
   101  
   102  	buf.WriteByte('[')
   103  	jsonx.AppendString(buf, agentRunID)
   104  	buf.WriteByte(',')
   105  	buf.WriteByte('{')
   106  	buf.WriteString(`"reservoir_size":`)
   107  	jsonx.AppendUint(buf, uint64(cap(events.events)))
   108  	buf.WriteByte(',')
   109  	buf.WriteString(`"events_seen":`)
   110  	jsonx.AppendUint(buf, uint64(events.numSeen))
   111  	buf.WriteByte('}')
   112  	buf.WriteByte(',')
   113  	buf.WriteByte('[')
   114  	for i, e := range events.events {
   115  		if i > 0 {
   116  			buf.WriteByte(',')
   117  		}
   118  		e.WriteJSON(buf)
   119  	}
   120  	buf.WriteByte(']')
   121  	buf.WriteByte(']')
   122  
   123  	return buf.Bytes(), nil
   124  
   125  }
   126  
   127  // split splits the events into two.  NOTE! The two event pools are not valid
   128  // priority queues, and should only be used to create JSON, not for adding any
   129  // events.
   130  func (events *analyticsEvents) split() (*analyticsEvents, *analyticsEvents) {
   131  	// numSeen is conserved: e1.numSeen + e2.numSeen == events.numSeen.
   132  	e1 := &analyticsEvents{
   133  		numSeen:        len(events.events) / 2,
   134  		events:         make([]analyticsEvent, len(events.events)/2),
   135  		failedHarvests: events.failedHarvests,
   136  	}
   137  	e2 := &analyticsEvents{
   138  		numSeen:        events.numSeen - e1.numSeen,
   139  		events:         make([]analyticsEvent, len(events.events)-len(e1.events)),
   140  		failedHarvests: events.failedHarvests,
   141  	}
   142  	// Note that slicing is not used to ensure that length == capacity for
   143  	// e1.events and e2.events.
   144  	copy(e1.events, events.events)
   145  	copy(e2.events, events.events[len(events.events)/2:])
   146  
   147  	return e1, e2
   148  }