github.com/newrelic/go-agent@v3.26.0+incompatible/internal/harvest.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  	"strings"
     8  	"sync"
     9  	"time"
    10  )
    11  
    12  // Harvestable is something that can be merged into a Harvest.
    13  type Harvestable interface {
    14  	MergeIntoHarvest(h *Harvest)
    15  }
    16  
    17  // HarvestTypes is a bit set used to indicate which data types are ready to be
    18  // reported.
    19  type HarvestTypes uint
    20  
    21  const (
    22  	// HarvestMetricsTraces is the Metrics Traces type
    23  	HarvestMetricsTraces HarvestTypes = 1 << iota
    24  	// HarvestSpanEvents is the Span Event type
    25  	HarvestSpanEvents
    26  	// HarvestCustomEvents is the Custom Event type
    27  	HarvestCustomEvents
    28  	// HarvestTxnEvents is the Transaction Event type
    29  	HarvestTxnEvents
    30  	// HarvestErrorEvents is the Error Event type
    31  	HarvestErrorEvents
    32  )
    33  
    34  const (
    35  	// HarvestTypesEvents includes all Event types
    36  	HarvestTypesEvents = HarvestSpanEvents | HarvestCustomEvents | HarvestTxnEvents | HarvestErrorEvents
    37  	// HarvestTypesAll includes all harvest types
    38  	HarvestTypesAll = HarvestMetricsTraces | HarvestTypesEvents
    39  )
    40  
    41  type harvestTimer struct {
    42  	periods     map[HarvestTypes]time.Duration
    43  	lastHarvest map[HarvestTypes]time.Time
    44  }
    45  
    46  func newHarvestTimer(now time.Time, periods map[HarvestTypes]time.Duration) *harvestTimer {
    47  	lastHarvest := make(map[HarvestTypes]time.Time, len(periods))
    48  	for tp := range periods {
    49  		lastHarvest[tp] = now
    50  	}
    51  	return &harvestTimer{periods: periods, lastHarvest: lastHarvest}
    52  }
    53  
    54  func (timer *harvestTimer) ready(now time.Time) (ready HarvestTypes) {
    55  	for tp, period := range timer.periods {
    56  		if deadline := timer.lastHarvest[tp].Add(period); now.After(deadline) {
    57  			timer.lastHarvest[tp] = deadline
    58  			ready |= tp
    59  		}
    60  	}
    61  	return
    62  }
    63  
    64  // Harvest contains collected data.
    65  type Harvest struct {
    66  	timer *harvestTimer
    67  
    68  	Metrics      *metricTable
    69  	ErrorTraces  harvestErrors
    70  	TxnTraces    *harvestTraces
    71  	SlowSQLs     *slowQueries
    72  	SpanEvents   *spanEvents
    73  	CustomEvents *customEvents
    74  	TxnEvents    *txnEvents
    75  	ErrorEvents  *errorEvents
    76  }
    77  
    78  const (
    79  	// txnEventPayloadlimit is the maximum number of events that should be
    80  	// sent up in one post.
    81  	txnEventPayloadlimit = 5000
    82  )
    83  
    84  // Ready returns a new Harvest which contains the data types ready for harvest,
    85  // or nil if no data is ready for harvest.
    86  func (h *Harvest) Ready(now time.Time) *Harvest {
    87  	ready := &Harvest{}
    88  
    89  	types := h.timer.ready(now)
    90  	if 0 == types {
    91  		return nil
    92  	}
    93  
    94  	if 0 != types&HarvestCustomEvents {
    95  		h.Metrics.addCount(customEventsSeen, h.CustomEvents.NumSeen(), forced)
    96  		h.Metrics.addCount(customEventsSent, h.CustomEvents.NumSaved(), forced)
    97  		ready.CustomEvents = h.CustomEvents
    98  		h.CustomEvents = newCustomEvents(h.CustomEvents.capacity())
    99  	}
   100  	if 0 != types&HarvestTxnEvents {
   101  		h.Metrics.addCount(txnEventsSeen, h.TxnEvents.NumSeen(), forced)
   102  		h.Metrics.addCount(txnEventsSent, h.TxnEvents.NumSaved(), forced)
   103  		ready.TxnEvents = h.TxnEvents
   104  		h.TxnEvents = newTxnEvents(h.TxnEvents.capacity())
   105  	}
   106  	if 0 != types&HarvestErrorEvents {
   107  		h.Metrics.addCount(errorEventsSeen, h.ErrorEvents.NumSeen(), forced)
   108  		h.Metrics.addCount(errorEventsSent, h.ErrorEvents.NumSaved(), forced)
   109  		ready.ErrorEvents = h.ErrorEvents
   110  		h.ErrorEvents = newErrorEvents(h.ErrorEvents.capacity())
   111  	}
   112  	if 0 != types&HarvestSpanEvents {
   113  		h.Metrics.addCount(spanEventsSeen, h.SpanEvents.NumSeen(), forced)
   114  		h.Metrics.addCount(spanEventsSent, h.SpanEvents.NumSaved(), forced)
   115  		ready.SpanEvents = h.SpanEvents
   116  		h.SpanEvents = newSpanEvents(h.SpanEvents.capacity())
   117  	}
   118  	// NOTE! Metrics must happen after the event harvest conditionals to
   119  	// ensure that the metrics contain the event supportability metrics.
   120  	if 0 != types&HarvestMetricsTraces {
   121  		ready.Metrics = h.Metrics
   122  		ready.ErrorTraces = h.ErrorTraces
   123  		ready.SlowSQLs = h.SlowSQLs
   124  		ready.TxnTraces = h.TxnTraces
   125  		h.Metrics = newMetricTable(maxMetrics, now)
   126  		h.ErrorTraces = newHarvestErrors(maxHarvestErrors)
   127  		h.SlowSQLs = newSlowQueries(maxHarvestSlowSQLs)
   128  		h.TxnTraces = newHarvestTraces()
   129  	}
   130  	return ready
   131  }
   132  
   133  // Payloads returns a slice of payload creators.
   134  func (h *Harvest) Payloads(splitLargeTxnEvents bool) (ps []PayloadCreator) {
   135  	if nil == h {
   136  		return
   137  	}
   138  	if nil != h.CustomEvents {
   139  		ps = append(ps, h.CustomEvents)
   140  	}
   141  	if nil != h.ErrorEvents {
   142  		ps = append(ps, h.ErrorEvents)
   143  	}
   144  	if nil != h.SpanEvents {
   145  		ps = append(ps, h.SpanEvents)
   146  	}
   147  	if nil != h.Metrics {
   148  		ps = append(ps, h.Metrics)
   149  	}
   150  	if nil != h.ErrorTraces {
   151  		ps = append(ps, h.ErrorTraces)
   152  	}
   153  	if nil != h.TxnTraces {
   154  		ps = append(ps, h.TxnTraces)
   155  	}
   156  	if nil != h.SlowSQLs {
   157  		ps = append(ps, h.SlowSQLs)
   158  	}
   159  	if nil != h.TxnEvents {
   160  		if splitLargeTxnEvents {
   161  			ps = append(ps, h.TxnEvents.payloads(txnEventPayloadlimit)...)
   162  		} else {
   163  			ps = append(ps, h.TxnEvents)
   164  		}
   165  	}
   166  	return
   167  }
   168  
   169  // MaxTxnEventer returns the maximum number of Transaction Events that should be reported per period
   170  type MaxTxnEventer interface {
   171  	MaxTxnEvents() int
   172  }
   173  
   174  // HarvestConfigurer contains information about the configured number of various
   175  // types of events as well as the Faster Event Harvest report period.
   176  // It is implemented by AppRun and DfltHarvestCfgr.
   177  type HarvestConfigurer interface {
   178  	// ReportPeriods returns a map from the bitset of harvest types to the period that those types should be reported
   179  	ReportPeriods() map[HarvestTypes]time.Duration
   180  	// MaxSpanEvents returns the maximum number of Span Events that should be reported per period
   181  	MaxSpanEvents() int
   182  	// MaxCustomEvents returns the maximum number of Custom Events that should be reported per period
   183  	MaxCustomEvents() int
   184  	// MaxErrorEvents returns the maximum number of Error Events that should be reported per period
   185  	MaxErrorEvents() int
   186  	MaxTxnEventer
   187  }
   188  
   189  // NewHarvest returns a new Harvest.
   190  func NewHarvest(now time.Time, configurer HarvestConfigurer) *Harvest {
   191  	return &Harvest{
   192  		timer:        newHarvestTimer(now, configurer.ReportPeriods()),
   193  		Metrics:      newMetricTable(maxMetrics, now),
   194  		ErrorTraces:  newHarvestErrors(maxHarvestErrors),
   195  		TxnTraces:    newHarvestTraces(),
   196  		SlowSQLs:     newSlowQueries(maxHarvestSlowSQLs),
   197  		SpanEvents:   newSpanEvents(configurer.MaxSpanEvents()),
   198  		CustomEvents: newCustomEvents(configurer.MaxCustomEvents()),
   199  		TxnEvents:    newTxnEvents(configurer.MaxTxnEvents()),
   200  		ErrorEvents:  newErrorEvents(configurer.MaxErrorEvents()),
   201  	}
   202  }
   203  
   204  var (
   205  	trackMutex   sync.Mutex
   206  	trackMetrics []string
   207  )
   208  
   209  // TrackUsage helps track which integration packages are used.
   210  func TrackUsage(s ...string) {
   211  	trackMutex.Lock()
   212  	defer trackMutex.Unlock()
   213  
   214  	m := "Supportability/" + strings.Join(s, "/")
   215  	trackMetrics = append(trackMetrics, m)
   216  }
   217  
   218  func createTrackUsageMetrics(metrics *metricTable) {
   219  	trackMutex.Lock()
   220  	defer trackMutex.Unlock()
   221  
   222  	for _, m := range trackMetrics {
   223  		metrics.addSingleCount(m, forced)
   224  	}
   225  }
   226  
   227  // CreateFinalMetrics creates extra metrics at harvest time.
   228  func (h *Harvest) CreateFinalMetrics(reply *ConnectReply, hc HarvestConfigurer) {
   229  	if nil == h {
   230  		return
   231  	}
   232  	// Metrics will be non-nil when harvesting metrics (regardless of
   233  	// whether or not there are any metrics to send).
   234  	if nil == h.Metrics {
   235  		return
   236  	}
   237  
   238  	h.Metrics.addSingleCount(instanceReporting, forced)
   239  
   240  	// Configurable event harvest supportability metrics:
   241  	// https://source.datanerd.us/agents/agent-specs/blob/master/Connect-LEGACY.md#event-harvest-config
   242  	period := reply.ConfigurablePeriod()
   243  	h.Metrics.addDuration(supportReportPeriod, "", period, period, forced)
   244  	h.Metrics.addValue(supportTxnEventLimit, "", float64(hc.MaxTxnEvents()), forced)
   245  	h.Metrics.addValue(supportCustomEventLimit, "", float64(hc.MaxCustomEvents()), forced)
   246  	h.Metrics.addValue(supportErrorEventLimit, "", float64(hc.MaxErrorEvents()), forced)
   247  	h.Metrics.addValue(supportSpanEventLimit, "", float64(hc.MaxSpanEvents()), forced)
   248  
   249  	createTrackUsageMetrics(h.Metrics)
   250  
   251  	h.Metrics = h.Metrics.ApplyRules(reply.MetricRules)
   252  }
   253  
   254  // PayloadCreator is a data type in the harvest.
   255  type PayloadCreator interface {
   256  	// In the event of a rpm request failure (hopefully simply an
   257  	// intermittent collector issue) the payload may be merged into the next
   258  	// time period's harvest.
   259  	Harvestable
   260  	// Data prepares JSON in the format expected by the collector endpoint.
   261  	// This method should return (nil, nil) if the payload is empty and no
   262  	// rpm request is necessary.
   263  	Data(agentRunID string, harvestStart time.Time) ([]byte, error)
   264  	// EndpointMethod is used for the "method" query parameter when posting
   265  	// the data.
   266  	EndpointMethod() string
   267  }
   268  
   269  func supportMetric(metrics *metricTable, b bool, metricName string) {
   270  	if b {
   271  		metrics.addSingleCount(metricName, forced)
   272  	}
   273  }
   274  
   275  // CreateTxnMetrics creates metrics for a transaction.
   276  func CreateTxnMetrics(args *TxnData, metrics *metricTable) {
   277  	withoutFirstSegment := removeFirstSegment(args.FinalName)
   278  
   279  	// Duration Metrics
   280  	var durationRollup string
   281  	var totalTimeRollup string
   282  	if args.IsWeb {
   283  		durationRollup = webRollup
   284  		totalTimeRollup = totalTimeWeb
   285  		metrics.addDuration(dispatcherMetric, "", args.Duration, 0, forced)
   286  	} else {
   287  		durationRollup = backgroundRollup
   288  		totalTimeRollup = totalTimeBackground
   289  	}
   290  
   291  	metrics.addDuration(args.FinalName, "", args.Duration, 0, forced)
   292  	metrics.addDuration(durationRollup, "", args.Duration, 0, forced)
   293  
   294  	metrics.addDuration(totalTimeRollup, "", args.TotalTime, args.TotalTime, forced)
   295  	metrics.addDuration(totalTimeRollup+"/"+withoutFirstSegment, "", args.TotalTime, args.TotalTime, unforced)
   296  
   297  	// Better CAT Metrics
   298  	if cat := args.BetterCAT; cat.Enabled {
   299  		caller := callerUnknown
   300  		if nil != cat.Inbound {
   301  			caller = cat.Inbound.payloadCaller
   302  		}
   303  		m := durationByCallerMetric(caller)
   304  		metrics.addDuration(m.all, "", args.Duration, args.Duration, unforced)
   305  		metrics.addDuration(m.webOrOther(args.IsWeb), "", args.Duration, args.Duration, unforced)
   306  
   307  		// Transport Duration Metric
   308  		if nil != cat.Inbound {
   309  			d := cat.Inbound.TransportDuration
   310  			m = transportDurationMetric(caller)
   311  			metrics.addDuration(m.all, "", d, d, unforced)
   312  			metrics.addDuration(m.webOrOther(args.IsWeb), "", d, d, unforced)
   313  		}
   314  
   315  		// CAT Error Metrics
   316  		if args.HasErrors() {
   317  			m = errorsByCallerMetric(caller)
   318  			metrics.addSingleCount(m.all, unforced)
   319  			metrics.addSingleCount(m.webOrOther(args.IsWeb), unforced)
   320  		}
   321  
   322  		supportMetric(metrics, args.AcceptPayloadSuccess, supportTracingAcceptSuccess)
   323  		supportMetric(metrics, args.AcceptPayloadException, supportTracingAcceptException)
   324  		supportMetric(metrics, args.AcceptPayloadParseException, supportTracingAcceptParseException)
   325  		supportMetric(metrics, args.AcceptPayloadCreateBeforeAccept, supportTracingCreateBeforeAccept)
   326  		supportMetric(metrics, args.AcceptPayloadIgnoredMultiple, supportTracingIgnoredMultiple)
   327  		supportMetric(metrics, args.AcceptPayloadIgnoredVersion, supportTracingIgnoredVersion)
   328  		supportMetric(metrics, args.AcceptPayloadUntrustedAccount, supportTracingAcceptUntrustedAccount)
   329  		supportMetric(metrics, args.AcceptPayloadNullPayload, supportTracingAcceptNull)
   330  		supportMetric(metrics, args.CreatePayloadSuccess, supportTracingCreatePayloadSuccess)
   331  		supportMetric(metrics, args.CreatePayloadException, supportTracingCreatePayloadException)
   332  	}
   333  
   334  	// Apdex Metrics
   335  	if args.Zone != ApdexNone {
   336  		metrics.addApdex(apdexRollup, "", args.ApdexThreshold, args.Zone, forced)
   337  
   338  		mname := apdexPrefix + withoutFirstSegment
   339  		metrics.addApdex(mname, "", args.ApdexThreshold, args.Zone, unforced)
   340  	}
   341  
   342  	// Error Metrics
   343  	if args.HasErrors() {
   344  		metrics.addSingleCount(errorsRollupMetric.all, forced)
   345  		metrics.addSingleCount(errorsRollupMetric.webOrOther(args.IsWeb), forced)
   346  		metrics.addSingleCount(errorsPrefix+args.FinalName, forced)
   347  	}
   348  
   349  	// Queueing Metrics
   350  	if args.Queuing > 0 {
   351  		metrics.addDuration(queueMetric, "", args.Queuing, args.Queuing, forced)
   352  	}
   353  }
   354  
   355  // DfltHarvestCfgr implements HarvestConfigurer for internal test cases, and for situations where we don't
   356  // have a ConnectReply, such as for serverless harvests
   357  type DfltHarvestCfgr struct {
   358  	reportPeriods   map[HarvestTypes]time.Duration
   359  	maxTxnEvents    *uint
   360  	maxSpanEvents   *uint
   361  	maxCustomEvents *uint
   362  	maxErrorEvents  *uint
   363  }
   364  
   365  // ReportPeriods returns a map from the bitset of harvest types to the period that those types should be reported
   366  func (d *DfltHarvestCfgr) ReportPeriods() map[HarvestTypes]time.Duration {
   367  	if d.reportPeriods != nil {
   368  		return d.reportPeriods
   369  	}
   370  	return map[HarvestTypes]time.Duration{HarvestTypesAll: FixedHarvestPeriod}
   371  }
   372  
   373  // MaxTxnEvents returns the maximum number of Transaction Events that should be reported per period
   374  func (d *DfltHarvestCfgr) MaxTxnEvents() int {
   375  	if d.maxTxnEvents != nil {
   376  		return int(*d.maxTxnEvents)
   377  	}
   378  	return MaxTxnEvents
   379  }
   380  
   381  // MaxSpanEvents returns the maximum number of Span Events that should be reported per period
   382  func (d *DfltHarvestCfgr) MaxSpanEvents() int {
   383  	if d.maxSpanEvents != nil {
   384  		return int(*d.maxSpanEvents)
   385  	}
   386  	return MaxSpanEvents
   387  }
   388  
   389  // MaxCustomEvents returns the maximum number of Custom Events that should be reported per period
   390  func (d *DfltHarvestCfgr) MaxCustomEvents() int {
   391  	if d.maxCustomEvents != nil {
   392  		return int(*d.maxCustomEvents)
   393  	}
   394  	return MaxCustomEvents
   395  }
   396  
   397  // MaxErrorEvents returns the maximum number of Error Events that should be reported per period
   398  func (d *DfltHarvestCfgr) MaxErrorEvents() int {
   399  	if d.maxErrorEvents != nil {
   400  		return int(*d.maxErrorEvents)
   401  	}
   402  	return MaxErrorEvents
   403  }