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 }