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 }