github.com/instana/go-sensor@v1.62.2-0.20240520081010-4919868049e1/recorder.go (about) 1 // (c) Copyright IBM Corp. 2021 2 // (c) Copyright Instana Inc. 2016 3 4 package instana 5 6 import ( 7 "context" 8 "fmt" 9 "sync" 10 "time" 11 ) 12 13 // A SpanRecorder handles all of the `RawSpan` data generated via an 14 // associated `Tracer` (see `NewStandardTracer`) instance. It also names 15 // the containing process and provides access to a straightforward tag map. 16 type SpanRecorder interface { 17 // Implementations must determine whether and where to store `span`. 18 RecordSpan(span *spanS) 19 // Flush forces sending any buffered finished spans 20 Flush(context.Context) error 21 } 22 23 // Recorder accepts spans, processes and queues them 24 // for delivery to the backend. 25 type Recorder struct { 26 sync.RWMutex 27 spans []Span 28 testMode bool 29 } 30 31 // NewRecorder initializes a new span recorder 32 func NewRecorder() *Recorder { 33 r := &Recorder{} 34 35 ticker := time.NewTicker(1 * time.Second) 36 go func() { 37 for range ticker.C { 38 if sensor.Agent().Ready() { 39 go r.Flush(context.Background()) 40 } 41 } 42 }() 43 44 return r 45 } 46 47 // NewTestRecorder initializes a new span recorder that keeps all collected 48 // until they are requested. This recorder does not send spans to the agent (used for testing) 49 func NewTestRecorder() *Recorder { 50 return &Recorder{ 51 testMode: true, 52 } 53 } 54 55 // RecordSpan accepts spans to be recorded and added to the span queue 56 // for eventual reporting to the host agent. 57 func (r *Recorder) RecordSpan(span *spanS) { 58 // If we're not announced and not in test mode then just 59 // return 60 if !r.testMode && !sensor.Agent().Ready() { 61 return 62 } 63 64 r.Lock() 65 defer r.Unlock() 66 67 if len(r.spans) == sensor.options.MaxBufferedSpans { 68 r.spans = r.spans[1:] 69 } 70 71 r.spans = append(r.spans, newSpan(span)) 72 73 if r.testMode || !sensor.Agent().Ready() { 74 return 75 } 76 77 if len(r.spans) >= sensor.options.ForceTransmissionStartingAt { 78 sensor.logger.Debug("forcing ", len(r.spans), "span(s) to the agent") 79 go r.Flush(context.Background()) 80 } 81 } 82 83 // QueuedSpansCount returns the number of queued spans 84 // 85 // Used only in tests currently. 86 func (r *Recorder) QueuedSpansCount() int { 87 r.RLock() 88 defer r.RUnlock() 89 return len(r.spans) 90 } 91 92 // GetQueuedSpans returns a copy of the queued spans and clears the queue. 93 func (r *Recorder) GetQueuedSpans() []Span { 94 r.Lock() 95 defer r.Unlock() 96 97 // Copy queued spans 98 queuedSpans := make([]Span, len(r.spans)) 99 copy(queuedSpans, r.spans) 100 101 // and clear out the source 102 r.clearQueuedSpans() 103 return queuedSpans 104 } 105 106 // Flush sends queued spans to the agent 107 func (r *Recorder) Flush(ctx context.Context) error { 108 spansToSend := r.GetQueuedSpans() 109 if len(spansToSend) == 0 { 110 return nil 111 } 112 113 if err := sensor.Agent().SendSpans(spansToSend); err != nil { 114 r.Lock() 115 defer r.Unlock() 116 117 // put failed spans in front of the queue to make sure they are evicted first 118 // whenever the queue length exceeds options.MaxBufferedSpans 119 r.spans = append(spansToSend, r.spans...) 120 121 return fmt.Errorf("failed to send collected spans to the agent: %s", err) 122 } 123 124 return nil 125 } 126 127 // clearQueuedSpans brings the span queue to empty/0/nada 128 // 129 // This function doesn't take the Lock so make sure to have 130 // the write lock before calling. 131 // This is meant to be called from GetQueuedSpans which handles 132 // locking. 133 func (r *Recorder) clearQueuedSpans() { 134 r.spans = r.spans[:0] 135 }