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  }