github.com/mier85/go-sensor@v1.30.1-0.20220920111756-9bf41b3bc7e0/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  	queueWhenNotReady bool
    31  	flushInterval     time.Duration
    32  }
    33  
    34  // RecorderOption lets you specify more options for the recorder
    35  type RecorderOption func(recorder *Recorder)
    36  
    37  // RecorderWithQueueWhenNotReady will enable queuing even when the sensor is not yet ready
    38  func RecorderWithQueueWhenNotReady() RecorderOption {
    39  	return func(recorder *Recorder) {
    40  		recorder.queueWhenNotReady = true
    41  	}
    42  }
    43  
    44  // RecorderWithFlushInterval lets you specify how often traces should be flushed
    45  func RecorderWithFlushInterval(flushInterval time.Duration) RecorderOption {
    46  	return func(recorder *Recorder) {
    47  		recorder.flushInterval = flushInterval
    48  	}
    49  }
    50  
    51  // NewRecorder initializes a new span recorder
    52  func NewRecorder(options ...RecorderOption) *Recorder {
    53  	r := &Recorder{
    54  		flushInterval:     1 * time.Second,
    55  		queueWhenNotReady: false,
    56  	}
    57  
    58  	for _, option := range options {
    59  		option(r)
    60  	}
    61  
    62  	ticker := time.NewTicker(r.flushInterval)
    63  	go func() {
    64  		for range ticker.C {
    65  			if sensor.Agent().Ready() {
    66  				go r.Flush(context.Background())
    67  			}
    68  		}
    69  	}()
    70  
    71  	return r
    72  }
    73  
    74  // NewTestRecorder initializes a new span recorder that keeps all collected
    75  // until they are requested. This recorder does not send spans to the agent (used for testing)
    76  func NewTestRecorder() *Recorder {
    77  	return &Recorder{
    78  		testMode: true,
    79  	}
    80  }
    81  
    82  // RecordSpan accepts spans to be recorded and added to the span queue
    83  // for eventual reporting to the host agent.
    84  func (r *Recorder) RecordSpan(span *spanS) {
    85  	// If we're not announced and not in test mode then just
    86  	// return
    87  	if !r.testMode && !sensor.Agent().Ready() && !r.queueWhenNotReady {
    88  		return
    89  	}
    90  
    91  	r.Lock()
    92  	defer r.Unlock()
    93  
    94  	if len(r.spans) == sensor.options.MaxBufferedSpans {
    95  		r.spans = r.spans[1:]
    96  	}
    97  
    98  	r.spans = append(r.spans, newSpan(span))
    99  
   100  	if r.testMode || !sensor.Agent().Ready() {
   101  		return
   102  	}
   103  
   104  	if len(r.spans) >= sensor.options.ForceTransmissionStartingAt {
   105  		sensor.logger.Debug("forcing ", len(r.spans), "span(s) to the agent")
   106  		go r.Flush(context.Background())
   107  	}
   108  }
   109  
   110  // QueuedSpansCount returns the number of queued spans
   111  //   Used only in tests currently.
   112  func (r *Recorder) QueuedSpansCount() int {
   113  	r.RLock()
   114  	defer r.RUnlock()
   115  	return len(r.spans)
   116  }
   117  
   118  // GetQueuedSpans returns a copy of the queued spans and clears the queue.
   119  func (r *Recorder) GetQueuedSpans() []Span {
   120  	r.Lock()
   121  	defer r.Unlock()
   122  
   123  	// Copy queued spans
   124  	queuedSpans := make([]Span, len(r.spans))
   125  	copy(queuedSpans, r.spans)
   126  
   127  	// and clear out the source
   128  	r.clearQueuedSpans()
   129  	return queuedSpans
   130  }
   131  
   132  // Flush sends queued spans to the agent
   133  func (r *Recorder) Flush(ctx context.Context) error {
   134  	spansToSend := r.GetQueuedSpans()
   135  	if len(spansToSend) == 0 {
   136  		return nil
   137  	}
   138  
   139  	if err := sensor.Agent().SendSpans(spansToSend); err != nil {
   140  		r.Lock()
   141  		defer r.Unlock()
   142  
   143  		// put failed spans in front of the queue to make sure they are evicted first
   144  		// whenever the queue length exceeds options.MaxBufferedSpans
   145  		r.spans = append(spansToSend, r.spans...)
   146  
   147  		return fmt.Errorf("failed to send collected spans to the agent: %s", err)
   148  	}
   149  
   150  	return nil
   151  }
   152  
   153  // clearQueuedSpans brings the span queue to empty/0/nada
   154  //   This function doesn't take the Lock so make sure to have
   155  //   the write lock before calling.
   156  //   This is meant to be called from GetQueuedSpans which handles
   157  //   locking.
   158  func (r *Recorder) clearQueuedSpans() {
   159  	r.spans = r.spans[:0]
   160  }