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 }