newrelic 5.13.0 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/harvest.js DELETED
@@ -1,718 +0,0 @@
1
- 'use strict'
2
-
3
- const a = require('async')
4
- const CollectorResponse = require('./collector/response')
5
- const logger = require('./logger').child({component: 'Harvest'})
6
-
7
- const FROM_MILLIS = 1e-3
8
- const NAMES = require('./metrics/names')
9
-
10
- /**
11
- * Collects, formats, and cleans up data for a single harvest endpoint.
12
- *
13
- * @private
14
- */
15
- class HarvestStep {
16
- constructor(harvest, endpoint, datasource) {
17
- this.harvest = harvest
18
- this.success = false
19
- this.datasource = datasource
20
- this.result = null
21
- this.returned = null
22
- this._endpoint = endpoint
23
- this._payloads = null
24
- }
25
-
26
- get agent() {
27
- return this.harvest.agent
28
- }
29
-
30
- get name() {
31
- return this._endpoint
32
- }
33
-
34
- /**
35
- * Assembles the payloads to be sent to the collector.
36
- *
37
- * @abstract
38
- * @protected
39
- */
40
- preparePayloads(runId, datasource, callback) { // eslint-disable-line no-unused-vars
41
- callback(null, this.preparePayloadsSync(runId, datasource))
42
- }
43
-
44
- /**
45
- * Synchronously assembles the payloads to be sent to the collector.
46
- *
47
- * @abstract
48
- * @protected
49
- */
50
- preparePayloadsSync(runId, datasource) { // eslint-disable-line no-unused-vars
51
- throw new Error(
52
- 'Synchronous payload preparation not implemented for ' + this._endpoint
53
- )
54
- }
55
-
56
- /**
57
- * Merges the indicated payload back into the aggregator for future collection.
58
- *
59
- * @abstract
60
- * @protected
61
- */
62
- mergePayload(payload, idx) { // eslint-disable-line no-unused-vars
63
- throw new Error('Payload merging not implemented for ' + this._endpoint)
64
- }
65
-
66
- /**
67
- * When the datasource is an `EventAggregator`, this method will generate
68
- * metrics around how many were seen and will be sent.
69
- *
70
- * @protected
71
- *
72
- * @param {string} metrics.SEEN - The name of the events seen metric.
73
- * @param {string} metrics.SENT - The name of the events sent metric.
74
- * @param {string} metrics.DROPPED - The name of the events dropped metric.
75
- */
76
- createSamplerMetrics(metrics) {
77
- // Create all the metrics.
78
- const seenMetric = this.agent.metrics.getOrCreateMetric(metrics.SEEN)
79
- const sentMetric = this.agent.metrics.getOrCreateMetric(metrics.SENT)
80
- const droppedMetric = this.agent.metrics.getOrCreateMetric(metrics.DROPPED)
81
-
82
- // Calculate our seen/sent/dropped counts and record them.
83
- const seen = this.datasource.seen
84
- const sent = this.datasource.length
85
- const dropped = seen - sent
86
- seenMetric.incrementCallCount(seen)
87
- sentMetric.incrementCallCount(sent)
88
- droppedMetric.incrementCallCount(dropped)
89
-
90
- // If we dropped any, let the customer know.
91
- if (dropped) {
92
- logger.warn('Dropped %d of %d datapoints for %s.', dropped, seen, this._endpoint)
93
- logger.warn('You may want to increase the limits for this event type.')
94
- }
95
- }
96
-
97
- /**
98
- * Gets the harvest step ready to send.
99
- */
100
- prepare(callback) {
101
- if (!this.datasource || this.datasource.length === 0) {
102
- logger.debug('No data to send to %s', this._endpoint)
103
- return setImmediate(callback)
104
- }
105
-
106
- this.preparePayloads(this.agent.config.run_id, this.datasource, (err, payloads) => {
107
- if (err) {
108
- logger.debug('Failed to prepare payloads for %s', this._endpoint)
109
- }
110
- this._payloads = payloads
111
- callback(err)
112
- })
113
- }
114
-
115
- /**
116
- * Synchronously gets the harvest step ready to send.
117
- */
118
- prepareSync() {
119
- if (!this.datasource || this.datasource.length === 0) {
120
- logger.debug('No data to send to %s', this._endpoint)
121
- return
122
- }
123
-
124
- try {
125
- this._payloads = this.preparePayloadsSync(this.agent.config.run_id, this.datasource)
126
- return this._payloads
127
- } catch (err) {
128
- logger.debug('Failed to prepare payloads for %s', this._endpoint)
129
- }
130
- }
131
-
132
- /**
133
- * Sends all prepared payloads to the collector.
134
- */
135
- send(callback) {
136
- if (!this._payloads) {
137
- logger.debug('Payloads were not generated for %s', this._endpoint)
138
- return setImmediate(callback)
139
- }
140
-
141
- // Send each of the payloads in series.
142
- const self = this
143
- a.eachOfSeries(self._payloads, function sendEachPayload(payload, i, cb) {
144
- logger.trace(
145
- 'Sending payload %d of %d to %s',
146
- i + 1,
147
- self._payloads.length,
148
- self._endpoint
149
- )
150
-
151
- // Send the payload to the collector.
152
- let callbackCalled = false
153
- self._doSend(payload, i, function oncedCallback(err) {
154
- if (callbackCalled) {
155
- logger.info(
156
- 'Onced callback called multiple times in ' + self._endpoint,
157
- err
158
- )
159
- return
160
- }
161
- callbackCalled = true
162
- logger.trace(
163
- 'Onced callback called first time for ' + self._endpoint,
164
- err
165
- )
166
-
167
- return cb(err)
168
- })
169
- }, function afterSendingAllPayloads(err) {
170
- if (!err) {
171
- self.success = true
172
- }
173
-
174
- logger.trace('Finished sending payloads for ' + self._endpoint)
175
-
176
- callback(err)
177
- })
178
- }
179
-
180
- /**
181
- * Performs actual payload sending and retrying.
182
- *
183
- * @private
184
- */
185
- _doSend(payload, i, callback) {
186
- const self = this
187
- self._trySend(payload, function afterSend(err, response) {
188
- if (err) {
189
- return callback(err)
190
- }
191
-
192
- // Are we clearing our data?
193
- if (!response.retainData) {
194
- self._payloads[i] = null
195
- } else {
196
- logger.info('Failed to submit data to New Relic, data held for redelivery.')
197
- }
198
-
199
- // Do we need to retry this endpoint right now?
200
- if (response.retryAfter) {
201
- const delay = response.retryAfter
202
- logger.info('Retrying sending to %s in %d ms', self._endpoint, delay)
203
- setTimeout(() => self._doSend(payload, i, callback), delay)
204
- return
205
- }
206
-
207
- // Done!
208
- self.result = response
209
- self.payload = response.payload
210
- callback()
211
- })
212
- }
213
-
214
- _trySend(payload, callback) {
215
- try {
216
- this.agent.collector[this._endpoint](payload, callback)
217
- } catch (err) {
218
- logger.warn(err, 'Failed to call collector method %s', this._endpoint)
219
- callback(err)
220
- }
221
- }
222
-
223
- /**
224
- * Finishes the harvest step, performing any final cleanup steps.
225
- */
226
- finalize(callback) {
227
- if (this._payloads) {
228
- this._payloads.forEach((payload, idx) => {
229
- if (payload) {
230
- this.mergePayload(payload, idx)
231
- }
232
- })
233
- }
234
-
235
- setImmediate(callback)
236
- }
237
- }
238
-
239
- // -------------------------------------------------------------------------- //
240
-
241
- class CustomEventsHarvest extends HarvestStep {
242
- constructor(harvest) {
243
- super(harvest, 'customEvents', harvest.agent.customEventAggregator)
244
- this.createSamplerMetrics(NAMES.CUSTOM_EVENTS)
245
- }
246
-
247
- preparePayloadsSync(runId, customEvents) {
248
- if (!customEvents || !customEvents.length) {
249
- logger.debug('No custom events to send.')
250
- return []
251
- }
252
-
253
- return [[runId, customEvents.toArray()]]
254
- }
255
-
256
- mergePayload() {
257
- this.agent.customEventAggregator.merge(this.datasource)
258
- }
259
- }
260
-
261
- // -------------------------------------------------------------------------- //
262
-
263
- class ErrorEventHarvest extends HarvestStep {
264
- constructor(harvest) {
265
- super(harvest, 'errorEvents', harvest.agent.errors.getQueue())
266
- this.createSamplerMetrics(NAMES.TRANSACTION_ERROR)
267
- }
268
-
269
- preparePayloadsSync(runId, errorQueue) {
270
- if (!errorQueue || !errorQueue.length) {
271
- logger.debug('No error events to send.')
272
- return []
273
- }
274
-
275
- const metrics = {
276
- reservoir_size: errorQueue.limit,
277
- events_seen: errorQueue.seen
278
- }
279
- return [[runId, metrics, errorQueue.toArray()]]
280
- }
281
-
282
- mergePayload() {
283
- this.agent.errors.mergeEvents(this.datasource)
284
- }
285
- }
286
-
287
- // -------------------------------------------------------------------------- //
288
-
289
- class ErrorTraceHarvest extends HarvestStep {
290
- constructor(harvest) {
291
- const errorAggr = harvest.agent.errors
292
- super(harvest, 'errorData', errorAggr.getErrors())
293
-
294
- const metrics = harvest.agent.metrics
295
-
296
- // Generate metrics for collected errors.
297
- if (errorAggr.getTotalUnexpectedErrorCount() > 0) {
298
- let count = errorAggr.getTotalUnexpectedErrorCount()
299
- metrics.getOrCreateMetric(NAMES.ERRORS.ALL).incrementCallCount(count)
300
-
301
- count = errorAggr.getUnexpectedWebTransactionsErrorCount()
302
- metrics.getOrCreateMetric(NAMES.ERRORS.WEB).incrementCallCount(count)
303
-
304
- count = errorAggr.getUnexpectedOtherTransactionsErrorCount()
305
- metrics.getOrCreateMetric(NAMES.ERRORS.OTHER).incrementCallCount(count)
306
- }
307
-
308
- const expectedCount = errorAggr.getTotalExpectedErrorCount()
309
- if (expectedCount > 0) {
310
- metrics.getOrCreateMetric(NAMES.ERRORS.EXPECTED).incrementCallCount(expectedCount)
311
- }
312
- }
313
-
314
- preparePayloadsSync(runId, errors) {
315
- if (!errors || !errors.length) {
316
- logger.debug('No error traces to send.')
317
- return []
318
- }
319
- return [[runId, errors]]
320
- }
321
-
322
- mergePayload() {
323
- this.agent.errors.mergeErrors(this.datasource)
324
- }
325
- }
326
-
327
- // -------------------------------------------------------------------------- //
328
-
329
- class MetricsHarvest extends HarvestStep {
330
- constructor(harvest) {
331
- super(harvest, 'metricData', harvest.agent.metrics)
332
- this._beginSeconds = harvest.agent.metrics.started * FROM_MILLIS
333
- this._endSeconds = Date.now() * FROM_MILLIS
334
- }
335
-
336
- preparePayloadsSync(runId, metrics) {
337
- if (!metrics || metrics.empty) {
338
- logger.debug('No metrics to send.')
339
- return []
340
- }
341
-
342
- return [[runId, this._beginSeconds, this._endSeconds, metrics.toJSON()]]
343
- }
344
-
345
- mergePayload() {
346
- this.agent.metrics.merge(this.datasource, true)
347
- }
348
-
349
- finalize(callback) {
350
- // The collector may send back metric naming rules for us to load.
351
- if (this.payload) {
352
- this.agent.mapper.load(this.payload)
353
- }
354
-
355
- super.finalize(callback)
356
- }
357
- }
358
-
359
- // -------------------------------------------------------------------------- //
360
-
361
- class QueryHarvest extends HarvestStep {
362
- constructor(harvest) {
363
- super(harvest, 'queryData', harvest.agent.queries)
364
- }
365
-
366
- preparePayloads(runId, queries, callback) {
367
- if (!queries || !queries.samples.size) {
368
- logger.debug('No queries to send.')
369
- return setImmediate(callback, null, [])
370
- }
371
-
372
- queries.prepareJSON((err, data) => callback(err, [[data]]))
373
- }
374
-
375
- preparePayloadsSync(runId, queries) {
376
- if (!queries || !queries.samples.size) {
377
- logger.debug('No queries to send.')
378
- return []
379
- }
380
-
381
- return [[queries.prepareJSONSync()]]
382
- }
383
-
384
- mergePayload() {
385
- this.agent.queries.merge(this.datasource)
386
- }
387
- }
388
-
389
- // -------------------------------------------------------------------------- //
390
-
391
- class SpanEventHarvest extends HarvestStep {
392
- constructor(harvest) {
393
- super(harvest, 'spanEvents', harvest.agent.spanEventAggregator.getQueue())
394
- this.createSamplerMetrics(NAMES.SPAN_EVENTS)
395
- }
396
-
397
- preparePayloadsSync(runId, spanQueue) {
398
- if (!spanQueue || !spanQueue.length) {
399
- logger.debug('No span events to send.')
400
- return []
401
- }
402
-
403
- const metrics = {
404
- reservoir_size: spanQueue.limit,
405
- events_seen: spanQueue.seen
406
- }
407
- return [[runId, metrics, spanQueue.toArray()]]
408
- }
409
-
410
- mergePayload() {
411
- this.agent.spanEventAggregator.mergeEvents(this.datasource)
412
- }
413
- }
414
-
415
- // -------------------------------------------------------------------------- //
416
-
417
- class TransactionEventHarvest extends HarvestStep {
418
- constructor(harvest) {
419
- super(harvest, 'analyticsEvents', harvest.agent.events)
420
- this.createSamplerMetrics(NAMES.EVENTS)
421
- this._mergeablePayloads = []
422
- }
423
- preparePayloadsSync(runId, events) {
424
- if (!events || !events.length) {
425
- logger.debug('No transaction events to send.')
426
- return []
427
- }
428
-
429
- const splits = _splitPayload(runId, events)
430
- const payloads = []
431
- splits.forEach((split) => {
432
- this._mergeablePayloads.push(split.toMerge)
433
- payloads.push(split.payload)
434
- })
435
- return payloads
436
- }
437
-
438
- mergePayload(payload, idx) {
439
- if (this._mergeablePayloads[idx]) {
440
- this.agent.events.merge(this._mergeablePayloads[idx])
441
- this._mergeablePayloads[idx] = null
442
- } else {
443
- logger.debug('Invalid idx (%d) provided for merging transaction events.', idx)
444
- }
445
- }
446
- }
447
-
448
- // -------------------------------------------------------------------------- //
449
-
450
- class TransactionTraceHarvest extends HarvestStep {
451
- constructor(harvest) {
452
- const traceAggr = harvest.agent.traces
453
- const maxTraceSegments = harvest.agent.config.max_trace_segments
454
- const traces = [].concat(traceAggr.syntheticsTraces)
455
-
456
- if (traceAggr.trace) {
457
- const trace = traceAggr.trace
458
- if (trace.segmentsSeen > maxTraceSegments) {
459
- logger.warn(
460
- 'Transaction %s (%s) contained %d segments, only collecting the first %d',
461
- trace.transaction.name,
462
- trace.transaction.id,
463
- trace.segmentsSeen,
464
- maxTraceSegments
465
- )
466
- }
467
- traceAggr.noTraceSubmitted = 0
468
- traces.push(trace)
469
- } else if (++traceAggr.noTraceSubmitted >= 5) {
470
- traceAggr.resetTimingTracker()
471
- }
472
-
473
- super(harvest, 'transactionSampleData', traces)
474
- this._traces = traces
475
- }
476
-
477
- preparePayloads(runId, traces, callback) {
478
- if (!traces.length) {
479
- logger.debug('No transaction traces to send.')
480
- return setImmediate(callback, null, [])
481
- }
482
-
483
- a.map(
484
- traces,
485
- (trace, cb) => trace.generateJSON(cb),
486
- (err, encodedTraces) => callback(err, [[runId, encodedTraces]])
487
- )
488
- }
489
-
490
- preparePayloadsSync(runId, traces) {
491
- if (!traces.length) {
492
- logger.debug('No transaction traces to send.')
493
- return []
494
- }
495
-
496
- return [[
497
- runId,
498
- traces.map((trace) => trace.generateJSONSync())
499
- ]]
500
- }
501
-
502
- mergePayload() {
503
- if (this._traces) {
504
- for (let i = 0; i < this._traces.length; ++i) {
505
- this.agent.traces.add(this._traces[i].transaction)
506
- }
507
- } else {
508
- logger.debug('No transaction traces to merge back.')
509
- }
510
- }
511
-
512
- finalize(callback) {
513
- if (this.success) {
514
- ++this.agent.traces.reported
515
- }
516
- super.finalize(callback)
517
- }
518
- }
519
-
520
- // -------------------------------------------------------------------------- //
521
-
522
- /**
523
- * Sequences harvest steps and manages the harvest cycle.
524
- *
525
- * @private
526
- */
527
- class Harvest {
528
- constructor(agent) {
529
- this.agent = agent
530
- this.startTime = Date.now()
531
- this._steps = Object.create(null)
532
- }
533
-
534
- static get ALL_ENDPOINTS() {
535
- return {
536
- customEvents: false,
537
- metrics: false,
538
- errorEvents: false,
539
- errorTraces: false,
540
- transactionTraces: false,
541
- transactionEvents: false,
542
- queries: false,
543
- spanEvents: false
544
- }
545
- }
546
-
547
- /**
548
- * Assembles all the harvest steps that this harvest will perform.
549
- *
550
- *
551
- * @param {object.<string,bool>} endpoints
552
- * A map indicating all the endpoints that
553
- */
554
- prepare(endpoints) {
555
- // Fetch references to configuration pieces to simplify checks below.
556
- const config = this.agent.config
557
- const ecConfig = config.error_collector
558
-
559
- // Create steps for each of the requested endpoints.
560
- if (endpoints.customEvents && config.custom_insights_events.enabled) {
561
- this._steps.customEvents = new CustomEventsHarvest(this)
562
- }
563
-
564
- if (endpoints.metrics) {
565
- this._steps.metrics = new MetricsHarvest(this)
566
- }
567
-
568
- if (endpoints.errorEvents && ecConfig.enabled && ecConfig.capture_events) {
569
- this._steps.errorEvents = new ErrorEventHarvest(this)
570
- }
571
- if (endpoints.errorTraces && config.collect_errors && ecConfig.enabled) {
572
- this._steps.errorTraces = new ErrorTraceHarvest(this)
573
- }
574
- if (
575
- endpoints.transactionTraces &&
576
- config.collect_traces &&
577
- config.transaction_tracer.enabled
578
- ) {
579
- this._steps.transactionTraces = new TransactionTraceHarvest(this)
580
- }
581
- if (endpoints.transactionEvents && config.transaction_events.enabled) {
582
- this._steps.transactionEvents = new TransactionEventHarvest(this)
583
- }
584
- if (endpoints.queries && config.slow_sql.enabled) {
585
- this._steps.queries = new QueryHarvest(this)
586
- }
587
- if (
588
- endpoints.spanEvents &&
589
- config.span_events.enabled &&
590
- config.distributed_tracing.enabled
591
- ) {
592
- this._steps.spanEvents = new SpanEventHarvest(this)
593
- }
594
-
595
- if (logger.traceEnabled()) {
596
- logger.trace(endpoints, 'Harvesting %j', Object.keys(this._steps))
597
- }
598
- }
599
-
600
- send(callback) {
601
- const self = this
602
-
603
- a.map(this._steps, function eachHarvestStep(step, cb) {
604
- logger.trace('Doing harvest step %s.', step.name)
605
- if (!self.agent.collector.isConnected()) {
606
- const connectionLostMessage = 'Connection to New Relic lost during harvest.'
607
- logger.warn(connectionLostMessage)
608
- return setImmediate(callback, new Error(connectionLostMessage))
609
- }
610
- a.series([
611
- step.prepare.bind(step),
612
- step.send.bind(step)
613
- ], function afterHarvestStep(err) {
614
- step.finalize(function afterFinalize(finalizeErr) {
615
- // log finalize errors as may be hidden by errors in other steps
616
- // and errors during finalize might result in incorrect data retention
617
- if (finalizeErr) {
618
- logger.warn(finalizeErr, 'Error during finalize of harvest step.')
619
- }
620
-
621
- cb(null, {
622
- error: err || finalizeErr || null,
623
- agentRun: step.result && step.result.agentRun
624
- })
625
- })
626
- })
627
- }, function afterAllHarvestSteps(err, results) {
628
- const BEHAVIOR = CollectorResponse.AGENT_RUN_BEHAVIOR
629
- let agentRunAction = BEHAVIOR.PRESERVE
630
-
631
- if (err) {
632
- // Any runtime errors should preserve the agent run.
633
- callback(err, agentRunAction)
634
- return
635
- }
636
-
637
- // Pull out and log any errors from harvest steps.
638
- const errors = results.map((r) => r.error).filter((e) => !!e)
639
- if (errors.length > 0) {
640
- logger.warn({errors}, 'Errors during harvest!')
641
- }
642
-
643
- // See if any endpoints told us to shutdown or restart. A shutdown trumps
644
- // everything, restart just trumps a preserve.
645
- for (let i = 0; i < results.length; ++i) {
646
- const agentRun = results[i].agentRun
647
- if (agentRun === BEHAVIOR.SHUTDOWN) {
648
- agentRunAction = BEHAVIOR.SHUTDOWN
649
- break
650
- } else if (agentRun === BEHAVIOR.RESTART) {
651
- agentRunAction = BEHAVIOR.RESTART
652
- }
653
- }
654
-
655
- callback(errors[0], agentRunAction)
656
- })
657
- }
658
-
659
- getPayloads() {
660
- const stepNames = Object.keys(this._steps)
661
- const harvest = this
662
- // This will only grab the first payload in a split payload case
663
- return stepNames.reduce(function processStep(processedSteps, name) {
664
- const payload = harvest._steps[name].prepareSync()
665
- if (payload) {
666
- processedSteps[name] = payload[0]
667
- }
668
- return processedSteps
669
- }, Object.create(null))
670
- }
671
- }
672
-
673
- function _splitPayload(runId, queue) {
674
- // If we're less than 1/3 full, don't bother splitting the payload.
675
- if (queue.length === 0) {
676
- return []
677
- }
678
- if (queue.length < queue.limit / 3) {
679
- return [{
680
- toMerge: queue,
681
- payload: [
682
- runId,
683
- {reservoir_size: queue.limit, events_seen: queue.seen},
684
- queue.toArray()
685
- ]
686
- }]
687
- }
688
-
689
- // Our payload is large, so split it in half.
690
- // TODO: update this to pull the priority off the event when DT is released
691
- const events = queue.getRawEvents()
692
- const size = Math.floor(queue.length / 2)
693
- const limit = Math.floor(queue.limit / 2)
694
- const seen = Math.floor(queue.seen / 2)
695
- const firstHalf = events.splice(0, size)
696
-
697
- return [{
698
- toMerge: firstHalf,
699
- payload: [
700
- runId,
701
- {reservoir_size: limit, events_seen: seen},
702
- firstHalf.map(rawEventsToValues)
703
- ]
704
- }, {
705
- toMerge: events,
706
- payload: [
707
- runId,
708
- {reservoir_size: queue.limit - limit, events_seen: queue.seen - seen},
709
- events.map(rawEventsToValues)
710
- ]
711
- }]
712
-
713
- function rawEventsToValues(ev) {
714
- return ev.value
715
- }
716
- }
717
-
718
- module.exports = Harvest