newrelic 5.9.1 → 5.13.1

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.
Files changed (55) hide show
  1. package/.travis.yml +14 -20
  2. package/CONTRIBUTING.md +4 -4
  3. package/NEWS.md +141 -0
  4. package/api.js +4 -3
  5. package/bin/cassandra-setup.sh +8 -0
  6. package/bin/run-versioned-tests.sh +3 -1
  7. package/bin/travis-install-mongo.sh +10 -6
  8. package/bin/travis-setup.sh +3 -1
  9. package/lib/agent.js +380 -307
  10. package/lib/aggregators/base-aggregator.js +111 -0
  11. package/lib/aggregators/event-aggregator.js +148 -0
  12. package/lib/aggregators/trace-aggregator.js +11 -0
  13. package/lib/collector/api.js +133 -48
  14. package/lib/collector/facts.js +10 -1
  15. package/lib/collector/http-agents.js +43 -4
  16. package/lib/collector/remote-method.js +2 -13
  17. package/lib/collector/serverless.js +34 -53
  18. package/lib/config/default.js +14 -1
  19. package/lib/config/env.js +13 -1
  20. package/lib/config/harvest-config-validator.js +26 -0
  21. package/lib/config/index.js +316 -56
  22. package/lib/config/lasp.js +5 -5
  23. package/lib/config/merge-server-config.js +56 -0
  24. package/lib/custom-events/custom-event-aggregator.js +31 -0
  25. package/lib/db/parsed-statement.js +1 -1
  26. package/lib/db/query-sample.js +110 -0
  27. package/lib/db/query-trace-aggregator.js +139 -0
  28. package/lib/db/slow-query.js +34 -0
  29. package/lib/errors/{aggregator.js → error-collector.js} +196 -149
  30. package/lib/errors/error-event-aggregator.js +36 -0
  31. package/lib/errors/error-trace-aggregator.js +59 -0
  32. package/lib/errors/helper.js +137 -0
  33. package/lib/errors/index.js +25 -35
  34. package/lib/injector.js +161 -0
  35. package/lib/instrumentation/@hapi/hapi.js +3 -0
  36. package/lib/instrumentation/core/globals.js +6 -13
  37. package/lib/instrumentation/core/http-outbound.js +3 -1
  38. package/lib/instrumentations.js +1 -0
  39. package/lib/metrics/index.js +4 -4
  40. package/lib/metrics/metric-aggregator.js +176 -0
  41. package/lib/metrics/names.js +14 -1
  42. package/lib/serverless/aws-lambda.js +33 -1
  43. package/lib/shimmer.js +120 -4
  44. package/lib/spans/span-event-aggregator.js +72 -0
  45. package/lib/transaction/index.js +47 -2
  46. package/lib/transaction/trace/aggregator.js +246 -145
  47. package/lib/transaction/trace/index.js +4 -4
  48. package/lib/transaction/transaction-event-aggregator.js +149 -0
  49. package/lib/util/urltils.js +23 -0
  50. package/lib/utilization/common.js +11 -4
  51. package/package.json +13 -11
  52. package/lib/db/tracer.js +0 -237
  53. package/lib/event-aggregator.js +0 -94
  54. package/lib/harvest.js +0 -708
  55. package/lib/spans/aggregator.js +0 -39
package/lib/agent.js CHANGED
@@ -4,27 +4,27 @@ const AdaptiveSampler = require('./adaptive-sampler')
4
4
  const CollectorAPI = require('./collector/api')
5
5
  const ServerlessCollector = require('./collector/serverless')
6
6
  const DESTINATIONS = require('./config/attribute-filter').DESTINATIONS
7
- const ErrorAggregator = require('./errors/aggregator')
7
+ const CustomEventAggregator = require('./custom-events/custom-event-aggregator')
8
+ const ErrorCollector = require('./errors/error-collector')
9
+ const ErrorTraceAggregator = require('./errors/error-trace-aggregator')
10
+ const ErrorEventAggregator = require('./errors/error-event-aggregator')
8
11
  const EventEmitter = require('events').EventEmitter
9
- const Harvest = require('./harvest')
10
12
  const hashes = require('./util/hashes')
11
13
  const logger = require('./logger')
12
14
  const MetricMapper = require('./metrics/mapper')
13
15
  const MetricNormalizer = require('./metrics/normalizer')
14
- const Metrics = require('./metrics')
16
+ const MetricAggregator = require('./metrics/metric-aggregator')
15
17
  const NAMES = require('./metrics/names')
16
- const PriorityQueue = require('./priority-queue')
17
- const QueryTracer = require('./db/tracer')
18
+ const QueryTraceAggregator = require('./db/query-trace-aggregator')
18
19
  const sampler = require('./sampler')
19
- const SpanAggregator = require('./spans/aggregator')
20
- const TraceAggregator = require('./transaction/trace/aggregator')
20
+ const TransactionTraceAggregator = require('./transaction/trace/aggregator')
21
+ const SpanEventAggregator = require('./spans/span-event-aggregator')
22
+ const TransactionEventAggregator = require('./transaction/transaction-event-aggregator')
21
23
  const Tracer = require('./transaction/tracer')
22
24
  const TxSegmentNormalizer = require('./metrics/normalizer/tx_segment')
23
25
  const uninstrumented = require('./uninstrumented')
24
26
  const util = require('util')
25
27
 
26
- const AGENT_RUN_BEHAVIOR = require('./collector/response').AGENT_RUN_BEHAVIOR
27
-
28
28
  // Map of valid states to whether or not data collection is valid
29
29
  const STATES = {
30
30
  stopped: false,
@@ -37,8 +37,9 @@ const STATES = {
37
37
  errored: false
38
38
  }
39
39
 
40
- // just to make clear what's going on
41
- const TO_MILLIS = 1e3
40
+ const MAX_ERROR_TRACES_DEFAULT = 20
41
+ const INITIAL_HARVEST_DELAY_MS = 1000
42
+ const DEFAULT_HARVEST_INTERVAL_MS = 60000
42
43
 
43
44
  /**
44
45
  * There's a lot of stuff in this constructor, due to Agent acting as the
@@ -65,24 +66,94 @@ function Agent(config) {
65
66
  this.collector = new CollectorAPI(this)
66
67
  }
67
68
 
68
- // Reset the agent to add all the sub-objects it needs. These object are the
69
- // ones that get re-created if the agent is told to restart from the collector.
70
- this.events = null
71
- this.customEvents = null
72
- this.errors = null
73
- this.mapper = null
74
- this.metricNameNormalizer = null
75
- this.metrics = null
76
- this.spans = null
77
- this.transactionNameNormalizer = null
78
- this.txSegmentNormalizer = null
79
- this.urlNormalizer = null
80
- this.userNormalizer = null
81
- this.reset()
69
+ this.mapper = new MetricMapper()
70
+ this.metricNameNormalizer = new MetricNormalizer(this.config, 'metric name')
71
+
72
+ this.metrics = new MetricAggregator(
73
+ {
74
+ periodMs: DEFAULT_HARVEST_INTERVAL_MS,
75
+ apdexT: this.config.apdex_t,
76
+ mapper: this.mapper,
77
+ normalizer: this.metricNameNormalizer
78
+ },
79
+ this.collector
80
+ )
81
+
82
+ this.metrics.on(
83
+ 'starting metric_data data send.',
84
+ this._beforeMetricDataSend.bind(this)
85
+ )
86
+
87
+ // Open tracing.
88
+ this.spanEventAggregator = new SpanEventAggregator({
89
+ periodMs: config.event_harvest_config.report_period_ms,
90
+ limit: config.event_harvest_config.harvest_limits.span_event_data
91
+ },
92
+ this.collector,
93
+ this.metrics)
94
+
95
+ this.transactionNameNormalizer = new MetricNormalizer(this.config, 'transaction name')
96
+ // Segment term based tx renaming for MGI mitigation.
97
+ this.txSegmentNormalizer = new TxSegmentNormalizer()
98
+
99
+ // User naming and ignoring rules.
100
+ this.urlNormalizer = new MetricNormalizer(this.config, 'URL')
101
+ this.userNormalizer = new MetricNormalizer(this.config, 'user')
102
+ this.userNormalizer.loadFromConfig()
103
+
104
+ this.transactionEventAggregator = new TransactionEventAggregator(
105
+ {
106
+ periodMs: config.event_harvest_config.report_period_ms,
107
+ limit: config.event_harvest_config.harvest_limits.analytic_event_data
108
+ },
109
+ this.collector,
110
+ this.metrics
111
+ )
112
+
113
+ this.customEventAggregator = new CustomEventAggregator(
114
+ {
115
+ periodMs: config.event_harvest_config.report_period_ms,
116
+ limit: config.event_harvest_config.harvest_limits.custom_event_data
117
+ },
118
+ this.collector,
119
+ this.metrics
120
+ )
121
+
122
+ const errorTraceAggregator = new ErrorTraceAggregator(
123
+ {
124
+ periodMs: DEFAULT_HARVEST_INTERVAL_MS,
125
+ limit: MAX_ERROR_TRACES_DEFAULT
126
+ },
127
+ this.collector
128
+ )
129
+
130
+ const errorEventAggregator = new ErrorEventAggregator(
131
+ {
132
+ periodMs: config.event_harvest_config.report_period_ms,
133
+ limit: config.event_harvest_config.harvest_limits.error_event_data
134
+ },
135
+ this.collector,
136
+ this.metrics
137
+ )
138
+
139
+ this.errors = new ErrorCollector(
140
+ config,
141
+ errorTraceAggregator,
142
+ errorEventAggregator,
143
+ this.metrics
144
+ )
82
145
 
83
146
  // Transaction tracing.
84
147
  this.tracer = new Tracer(this)
85
- this.traces = new TraceAggregator(this.config)
148
+ this.traces = new TransactionTraceAggregator(
149
+ {
150
+ periodMs: DEFAULT_HARVEST_INTERVAL_MS,
151
+ config: this.config,
152
+ isAsync: !config.serverless_mode.enabled,
153
+ method: 'transaction_sample_data'
154
+ },
155
+ this.collector
156
+ )
86
157
  this.transactionSampler = new AdaptiveSampler({
87
158
  agent: this,
88
159
  serverless: config.serverless_mode.enabled,
@@ -90,8 +161,15 @@ function Agent(config) {
90
161
  target: config.sampling_target
91
162
  })
92
163
 
93
- // Query tracing.
94
- this.queries = new QueryTracer(this.config)
164
+ this.queries = new QueryTraceAggregator(
165
+ {
166
+ config: this.config,
167
+ periodMs: DEFAULT_HARVEST_INTERVAL_MS,
168
+ method: 'sql_trace_data',
169
+ isAsync: !config.serverless_mode.enabled
170
+ },
171
+ this.collector
172
+ )
95
173
 
96
174
  // Set up all the configuration events the agent needs to listen for.
97
175
  this._listenForConfigChanges()
@@ -100,12 +178,8 @@ function Agent(config) {
100
178
  this.totalActiveSegments = 0
101
179
  this.segmentsCreatedInHarvest = 0
102
180
  this.segmentsClearedInHarvest = 0
181
+ // Used by shutdown code as well as entity tracking stats
103
182
  this.activeTransactions = 0
104
- this.transactionsCreatedInHarvest = 0
105
-
106
- // Harvest attributes.
107
- this.harvesterHandle = null
108
- this._lastHarvest = null
109
183
 
110
184
  // Finally, add listeners for the agent's own events.
111
185
  this.on('transactionFinished', this._transactionFinished.bind(this))
@@ -153,10 +227,9 @@ Agent.prototype.start = function start(callback) {
153
227
  callback(new Error('Not starting without license key!'))
154
228
  })
155
229
  }
156
-
157
230
  logger.info('Starting New Relic for Node.js connection process.')
158
231
 
159
- this.collector.connect(function onConnect(error, response) {
232
+ this.collector.connect(function onStartConnect(error, response) {
160
233
  if (error || response.shouldShutdownRun()) {
161
234
  agent.setState('errored')
162
235
  sampler.stop()
@@ -168,10 +241,12 @@ Agent.prototype.start = function start(callback) {
168
241
  }
169
242
 
170
243
  if (agent.collector.isConnected()) {
244
+ agent.onConnect()
171
245
  agent.setState('started')
172
246
  const config = response.payload
247
+
173
248
  if (agent.config.no_immediate_harvest) {
174
- agent._scheduleHarvester(agent.config.data_report_period)
249
+ agent.startAggregators()
175
250
  callback(null, config)
176
251
  } else {
177
252
  // Harvest immediately for quicker data display, but after at least 1
@@ -180,10 +255,13 @@ Agent.prototype.start = function start(callback) {
180
255
  // NOTE: this setTimeout is deliberately NOT unref'd due to it being
181
256
  // the last step in the Agent startup process
182
257
  setTimeout(function afterTimeout() {
183
- agent.harvest(function onHarvest(harvestError) {
184
- callback(harvestError, config)
258
+ logger.info(`Starting initial ${INITIAL_HARVEST_DELAY_MS}ms harvest.`)
259
+
260
+ agent.forceHarvestAll(function afterAllAggregatorsSend() {
261
+ agent.startAggregators()
262
+ callback(null, config)
185
263
  })
186
- }, 1000)
264
+ }, INITIAL_HARVEST_DELAY_MS)
187
265
  }
188
266
  } else {
189
267
  callback(new Error('Collector did not connect and did not error'))
@@ -191,6 +269,181 @@ Agent.prototype.start = function start(callback) {
191
269
  })
192
270
  }
193
271
 
272
+ /**
273
+ * Forces all aggregators to send the data collected.
274
+ * @param {Function} callback The callback to invoke when all data types have been sent.
275
+ */
276
+ Agent.prototype.forceHarvestAll = function forceHarvestAll(callback) {
277
+ const agent = this
278
+ const promises = []
279
+
280
+ const metricPromise = new Promise((resolve) => {
281
+ agent.metrics.once(
282
+ 'finished metric_data data send.',
283
+ function onMetricsFinished() {
284
+ resolve()
285
+ }
286
+ )
287
+ agent.metrics.send()
288
+ })
289
+
290
+ promises.push(metricPromise)
291
+
292
+ // TODO: plumb config through to aggregators so they can do their own checking.
293
+ if (agent.config.distributed_tracing.enabled &&
294
+ agent.config.span_events.enabled) {
295
+ const spanPromise = new Promise((resolve) => {
296
+ agent.spanEventAggregator.once(
297
+ 'finished span_event_data data send.',
298
+ function onSpansFinished() {
299
+ resolve()
300
+ }
301
+ )
302
+ agent.spanEventAggregator.send()
303
+ })
304
+
305
+ promises.push(spanPromise)
306
+ }
307
+
308
+ if (agent.config.custom_insights_events.enabled) {
309
+ const customEventPromise = new Promise((resolve) => {
310
+ agent.customEventAggregator.once(
311
+ 'finished custom_event_data data send.',
312
+ function onCustomEventsFinished() {
313
+ resolve()
314
+ }
315
+ )
316
+ agent.customEventAggregator.send()
317
+ })
318
+
319
+ promises.push(customEventPromise)
320
+ }
321
+
322
+ if (agent.config.transaction_events.enabled) {
323
+ const transactionEventPromise = new Promise((resolve) => {
324
+ agent.transactionEventAggregator.once(
325
+ 'finished analytic_event_data data send.',
326
+ function onTransactionEventsFinished() {
327
+ resolve()
328
+ }
329
+ )
330
+ agent.transactionEventAggregator.send()
331
+ })
332
+
333
+ promises.push(transactionEventPromise)
334
+ }
335
+
336
+ if (agent.config.transaction_tracer.enabled && agent.config.collect_traces) {
337
+ const transactionTracePromise = new Promise((resolve) => {
338
+ agent.traces.once(
339
+ 'finished transaction_sample_data data send.',
340
+ function onTracesFinished() {
341
+ resolve()
342
+ }
343
+ )
344
+ agent.traces.send()
345
+ })
346
+
347
+ promises.push(transactionTracePromise)
348
+ }
349
+
350
+ if (agent.config.slow_sql.enabled) {
351
+ const sqlTracePromise = new Promise((resolve) => {
352
+ agent.queries.once(
353
+ 'finished sql_trace_data data send.',
354
+ function onSqlTracesFinished() {
355
+ resolve()
356
+ }
357
+ )
358
+ agent.queries.send()
359
+ })
360
+
361
+ promises.push(sqlTracePromise)
362
+ }
363
+
364
+ const errorCollectorEnabled =
365
+ agent.config.error_collector && agent.config.error_collector.enabled
366
+
367
+ if (errorCollectorEnabled && agent.config.collect_errors) {
368
+ const errorTracePromise = new Promise((resolve) => {
369
+ agent.errors.traceAggregator.once(
370
+ 'finished error_data data send.',
371
+ function onErrorTracesFinished() {
372
+ resolve()
373
+ }
374
+ )
375
+ agent.errors.traceAggregator.send()
376
+ })
377
+
378
+ promises.push(errorTracePromise)
379
+ }
380
+
381
+ if (errorCollectorEnabled && agent.config.error_collector.capture_events) {
382
+ const errorEventPromise = new Promise((resolve) => {
383
+ agent.errors.eventAggregator.once(
384
+ 'finished error_event_data data send.',
385
+ function onErrorEventsFinished() {
386
+ resolve()
387
+ }
388
+ )
389
+ agent.errors.eventAggregator.send()
390
+ })
391
+
392
+ promises.push(errorEventPromise)
393
+ }
394
+
395
+ Promise.all(promises).then(() => {
396
+ // Get out of the promise so callback errors aren't treated as
397
+ // promise rejections.
398
+ setImmediate(callback)
399
+ })
400
+ }
401
+
402
+ Agent.prototype.stopAggregators = function stopAggregators() {
403
+ this.metrics.stop()
404
+ this.errors.stop()
405
+ this.traces.stop()
406
+ this.queries.stop()
407
+ this.spanEventAggregator.stop()
408
+ this.transactionEventAggregator.stop()
409
+ this.customEventAggregator.stop()
410
+ }
411
+
412
+ Agent.prototype.startAggregators = function startAggregators() {
413
+ this.metrics.start()
414
+ this.errors.start()
415
+ if (this.config.transaction_tracer.enabled && this.config.collect_traces) {
416
+ this.traces.start()
417
+ }
418
+
419
+ if (this.config.slow_sql.enabled) {
420
+ this.queries.start()
421
+ }
422
+
423
+ if (this.config.distributed_tracing.enabled &&
424
+ this.config.span_events.enabled) {
425
+ this.spanEventAggregator.start()
426
+ }
427
+
428
+ if (this.config.transaction_events.enabled) {
429
+ this.transactionEventAggregator.start()
430
+ }
431
+
432
+ if (this.config.custom_insights_events.enabled) {
433
+ this.customEventAggregator.start()
434
+ }
435
+ }
436
+
437
+ Agent.prototype.onConnect = function onConnect() {
438
+ this.metrics.reconfigure(this.config)
439
+ this.errors.reconfigure(this.config)
440
+ this.traces.reconfigure(this.config)
441
+ this.queries.reconfigure(this.config)
442
+ this.spanEventAggregator.reconfigure(this.config)
443
+ this.transactionEventAggregator.reconfigure(this.config)
444
+ this.customEventAggregator.reconfigure(this.config)
445
+ }
446
+
194
447
  /**
195
448
  * Bypasses standard collector connection by immediately invoking the startup
196
449
  * callback, after gathering local environment details.
@@ -218,7 +471,9 @@ Agent.prototype.stop = function stop(callback) {
218
471
  const agent = this
219
472
 
220
473
  this.setState('stopping')
221
- this._stopHarvester()
474
+
475
+ this.stopAggregators()
476
+
222
477
  sampler.stop()
223
478
 
224
479
  if (this.collector.isConnected()) {
@@ -234,32 +489,24 @@ Agent.prototype.stop = function stop(callback) {
234
489
  callback(error)
235
490
  })
236
491
  } else {
492
+ logger.trace('Collector was not connected, invoking callback.')
493
+
237
494
  process.nextTick(callback)
238
495
  }
239
496
  }
240
497
 
241
498
  /**
242
499
  * Resets queries.
243
- *
244
- * @param {boolean} forceReset
245
- * Flag signalling unconditional reset, sent during LASP application.
246
500
  */
247
- Agent.prototype._resetQueries = function resetQueries(forceReset) {
248
- if (!this.queries || forceReset) {
249
- this.queries = new QueryTracer(this.config)
250
- }
501
+ Agent.prototype._resetQueries = function resetQueries() {
502
+ this.queries.clear()
251
503
  }
252
504
 
253
- /**
254
- * Resets errors.
255
- *
256
- * @param {boolean} forceReset
257
- * Flag signalling unconditional reset, sent during LASP application.
258
- */
259
- Agent.prototype._resetErrors = function resetErrors(forceReset) {
260
- if (!this.errors || forceReset) {
261
- this.errors = new ErrorAggregator(this.config)
262
- }
505
+ Agent.prototype._resetErrors = function resetErrors() {
506
+ this.errors.clearAll()
507
+
508
+ // TODO: is this still necessary?
509
+ // Likely do more direct with new config
263
510
  this.errors.reconfigure(this.config)
264
511
  }
265
512
 
@@ -267,15 +514,7 @@ Agent.prototype._resetErrors = function resetErrors(forceReset) {
267
514
  * Resets events.
268
515
  */
269
516
  Agent.prototype._resetEvents = function resetEvents() {
270
- if (!this.events) {
271
- this.events = new PriorityQueue()
272
- }
273
-
274
- this.events.setLimit(this.config.transaction_events.max_samples_per_minute)
275
-
276
- if (!this.customEvents) {
277
- this.customEvents = new PriorityQueue()
278
- }
517
+ this.transactionEventAggregator.clear()
279
518
  }
280
519
 
281
520
  /**
@@ -284,105 +523,8 @@ Agent.prototype._resetEvents = function resetEvents() {
284
523
  * @param {boolean} forceReset
285
524
  * Flag signalling unconditional reset, sent during LASP application.
286
525
  */
287
- Agent.prototype._resetCustomEvents = function resetCustomEvents(forceReset) {
288
- if (!this.customEvents || forceReset) {
289
- this.customEvents = new PriorityQueue()
290
- }
291
-
292
- this.customEvents.setLimit(this.config.custom_insights_events.max_samples_stored)
293
- }
294
-
295
- /**
296
- * Builds all of the sub-properties of the agent that rely on configurations.
297
- */
298
- Agent.prototype.reset = function reset() {
299
- // Insights events.
300
- this._resetEvents()
301
- this._resetCustomEvents()
302
-
303
- // Error tracing.
304
- this._resetErrors()
305
-
306
- // Open tracing.
307
- this.spans = new SpanAggregator()
308
-
309
- // Metrics.
310
- this.mapper = new MetricMapper()
311
- this.metricNameNormalizer = new MetricNormalizer(this.config, 'metric name')
312
- this.metrics = new Metrics(this.config.apdex_t, this.mapper, this.metricNameNormalizer)
313
-
314
- // Transaction naming.
315
- this.transactionNameNormalizer = new MetricNormalizer(this.config, 'transaction name')
316
- this.urlNormalizer = new MetricNormalizer(this.config, 'URL')
317
-
318
- // Segment term based tx renaming for MGI mitigation.
319
- this.txSegmentNormalizer = new TxSegmentNormalizer()
320
-
321
- // User naming and ignoring rules.
322
- this.userNormalizer = new MetricNormalizer(this.config, 'user')
323
- this.userNormalizer.loadFromConfig()
324
- }
325
-
326
- /**
327
- * On agent startup, an interval timer is started that calls this method once
328
- * a minute, which in turn invokes the pieces of the harvest cycle. It calls
329
- * the various collector API methods in order, bailing out if one of them fails,
330
- * to ensure that the agents don't pummel the collector if it's already
331
- * struggling.
332
- */
333
- Agent.prototype.harvest = function harvest(callback) {
334
- if (!callback) {
335
- throw new TypeError('callback required!')
336
- }
337
-
338
- // Generate metrics for this harvest and then check we are connected to the
339
- // collector.
340
- this._generateHarvestMetrics()
341
- if (!this.collector.isConnected()) {
342
- return setImmediate(function immediatelyError() {
343
- callback(new Error('Not connected to New Relic!'))
344
- })
345
- }
346
-
347
- // We have a connection, create a new harvest.
348
- this.emit('harvestStarted')
349
- this._lastHarvest = new Harvest(this)
350
- this._lastHarvest.prepare(Harvest.ALL_ENDPOINTS)
351
-
352
- // Reset all our collections. The harvest has all the data it needs at this point.
353
- this._resetHarvestables()
354
-
355
- // Send the harvest!
356
- const collector = this.collector
357
- const agent = this
358
- this._lastHarvest.send(function afterHarvest(err, agentRunAction) {
359
- // Do we need to do anything to the agent run?
360
- if (agentRunAction === AGENT_RUN_BEHAVIOR.SHUTDOWN) {
361
- agent.emit('harvestFinished')
362
- agent.stop(function afterStop(stopError) {
363
- const shutdownError = stopError || err
364
- callback(shutdownError)
365
- })
366
- } else if (agentRunAction === AGENT_RUN_BEHAVIOR.RESTART) {
367
- collector.restart(function afterRestart(restartError) {
368
- // TODO: What if preconnect/connect respond with shutdown here?
369
- if (restartError) {
370
- logger.warn('Failed to restart agent run after harvest')
371
- callback(restartError)
372
- } else {
373
- _finish(err)
374
- }
375
- })
376
- } else {
377
- _finish(err)
378
- }
379
-
380
- function _finish(error) {
381
- agent.emit('harvestFinished')
382
- agent._scheduleHarvester(agent.config.data_report_period)
383
- callback(error)
384
- }
385
- })
526
+ Agent.prototype._resetCustomEvents = function resetCustomEvents() {
527
+ this.customEventAggregator.clear()
386
528
  }
387
529
 
388
530
  /**
@@ -391,78 +533,57 @@ Agent.prototype.harvest = function harvest(callback) {
391
533
  * NOTE: this doesn't currently work outside of serverless mode.
392
534
  */
393
535
  Agent.prototype.harvestSync = function harvestSync() {
394
- // Generate metrics for this harvest and then check we are connected to the
395
- // collector.
396
- this._generateHarvestMetrics()
536
+ logger.trace('Peparing to harvest.')
397
537
 
398
538
  if (!this.collector.isConnected()) {
399
- throw new Error('Not connected to New Relic!')
539
+ throw new Error('Sync harvest not connected/enabled!')
400
540
  }
401
541
 
402
542
  // We have a connection, create a new harvest.
403
543
  this.emit('harvestStarted')
404
- this._lastHarvest = new Harvest(this)
405
- this._lastHarvest.prepare(Harvest.ALL_ENDPOINTS)
406
-
407
- // Reset all our collections. The harvest has all the data it needs at this point.
408
- this._resetHarvestables()
544
+ logger.info('Harvest started.')
409
545
 
410
546
  const collector = this.collector
411
547
  const agent = this
412
548
 
413
- // Collect and send payload data
414
- const payloads = this._lastHarvest.getPayloads()
415
- collector.populateDataSync(payloads)
549
+ // "Sends" data to the serverless collector collection
550
+ this.metrics.send()
551
+ this.errors.traceAggregator.send()
552
+ this.errors.eventAggregator.send()
553
+ this.traces.send()
554
+ this.queries.send()
555
+ this.spanEventAggregator.send()
556
+ this.transactionEventAggregator.send()
557
+ this.customEventAggregator.send()
558
+
559
+ // Write serverless output
416
560
  collector.flushPayloadSync()
561
+
417
562
  agent.emit('harvestFinished')
563
+ logger.info('Harvest finished.')
418
564
  }
419
565
 
420
- Agent.prototype._generateHarvestMetrics = function _generateHarvestMetrics() {
566
+ Agent.prototype._beforeMetricDataSend = function _beforeMetricDataSend() {
567
+ this._generateEntityStatsAndClear()
568
+
569
+ // Send uninstrumented supportability metrics every metric harvest cycle
570
+ uninstrumented.createMetrics(this.metrics)
571
+ }
572
+
573
+ Agent.prototype._generateEntityStatsAndClear = function _generateHarvestMetrics() {
421
574
  // Note some information about the size of this harvest.
422
575
  if (logger.traceEnabled()) {
423
576
  logger.trace({
424
577
  segmentTotal: this.totalActiveSegments,
425
578
  harvestCreated: this.segmentsCreatedInHarvest,
426
579
  harvestCleared: this.segmentsClearedInHarvest,
427
- activeTransactions: this.activeTransactions,
428
- spansCollected: this.spans.length,
429
- spansSeen: this.spans.seen
430
- }, 'Entity stats on harvest')
431
- }
432
- this.recordSupportability(
433
- 'Nodejs/Transactions/Created',
434
- this.transactionsCreatedInHarvest
435
- )
436
-
437
- // Send uninstrumented supportability metrics every harvest cycle
438
- uninstrumented.createMetrics(this.metrics)
580
+ activeTransactions: this.activeTransactions
581
+ }, 'Entity stats on metric harvest')
582
+ }
439
583
 
440
584
  // Reset the counters.
441
585
  this.segmentsCreatedInHarvest = 0
442
586
  this.segmentsClearedInHarvest = 0
443
- this.transactionsCreatedInHarvest = 0
444
- }
445
-
446
- Agent.prototype._resetHarvestables = function _resetHarvestables() {
447
- // TODO: Make each aggregator able to compose its own payload and clean itself
448
- // up. Then the Harvest class can just iterate over all aggregations without
449
- // having to know bespoke reset information.
450
- this.metrics = new Metrics(
451
- this.config.apdex_t,
452
- this.mapper,
453
- this.metricNameNormalizer
454
- )
455
- this.events = new PriorityQueue(this.config.transaction_events.max_samples_per_minute)
456
-
457
- this.customEvents = new PriorityQueue(
458
- this.config.custom_insights_events.max_samples_stored
459
- )
460
-
461
- this.errors.clearEvents()
462
- this.errors.clearErrors()
463
- this.traces.reset()
464
- this.queries = new QueryTracer(this.config)
465
- this.spans.clearEvents()
466
587
  }
467
588
 
468
589
  /**
@@ -489,7 +610,7 @@ Agent.prototype.setState = function setState(newState) {
489
610
  throw new TypeError('Invalid state ' + newState)
490
611
  }
491
612
 
492
- logger.debug('Agent state changed from %s to %s.', this._state, newState)
613
+ logger.info('Agent state changed from %s to %s.', this._state, newState)
493
614
  this._state = newState
494
615
  this.emit(this._state)
495
616
  }
@@ -502,90 +623,6 @@ Agent.prototype.canCollectData = function canCollectData() {
502
623
  return STATES[this._state]
503
624
  }
504
625
 
505
- /**
506
- * Server-side configuration value.
507
- *
508
- * @param {number} apdexT Apdex tolerating value, in seconds.
509
- */
510
- Agent.prototype._apdexTChange = function _apdexTChange(apdexT) {
511
- logger.debug('Apdex tolerating value changed to %s.', apdexT)
512
- this.metrics.apdexT = apdexT
513
- }
514
-
515
- /**
516
- * Server-side configuration value. When run, forces a harvest cycle
517
- * so as to not cause the agent to go too long without reporting.
518
- *
519
- * @param {number} interval Time in seconds between harvest runs.
520
- */
521
- Agent.prototype._harvesterIntervalChange = _harvesterIntervalChange
522
-
523
- function _harvesterIntervalChange(interval, callback) {
524
- const agent = this
525
-
526
- // only change the setup if the harvester is currently running
527
- if (this.harvesterHandle) {
528
- // force a harvest now, to be safe
529
- this.harvest(function onHarvest(error) {
530
- agent._restartHarvester(interval)
531
- if (callback) callback(error)
532
- })
533
- } else if (callback) {
534
- process.nextTick(callback)
535
- }
536
- }
537
-
538
- /**
539
- * Restart the harvest cycle timer.
540
- *
541
- * @param {number} harvestSeconds How many seconds between harvests.
542
- */
543
- Agent.prototype._restartHarvester = function _restartHarvester(harvestSeconds) {
544
- this._stopHarvester()
545
- this._scheduleHarvester(harvestSeconds)
546
- }
547
-
548
- /**
549
- * Safely stop the harvest cycle timer.
550
- */
551
- Agent.prototype._stopHarvester = function _stopHarvester() {
552
- if (this.harvesterHandle) {
553
- clearTimeout(this.harvesterHandle)
554
- }
555
- this._lastHarvest = null
556
- this.harvesterHandle = null
557
- }
558
-
559
- /**
560
- * Safely start the harvest cycle timer, and ensure that the harvest
561
- * cycle won't keep an application from exiting if nothing else is
562
- * happening to keep it up.
563
- *
564
- * @param {number} harvestSeconds - How many seconds between harvests.
565
- */
566
- Agent.prototype._scheduleHarvester = function _scheduleHarvester(harvestSeconds) {
567
- const agent = this
568
- let harvestDelay = harvestSeconds * TO_MILLIS
569
-
570
- // If there was a previous harvest, we want to schedule the next one based on
571
- // its start time.
572
- if (this._lastHarvest && this._lastHarvest.startTime) {
573
- const timeSinceHarvest = Date.now() - this._lastHarvest.startTime
574
- harvestDelay = Math.max(0, harvestDelay - timeSinceHarvest)
575
- }
576
-
577
- this.harvesterHandle = setTimeout(function doHarvest() {
578
- // Agent#harvest handles scheduling the next harvest and properly reacting to
579
- // any errors or commands. All we need to do is note any errors it spits out.
580
- agent.harvest(function harvestError(error) {
581
- if (error) {
582
- logger.warn(error, 'Error on submission to New Relic.')
583
- }
584
- })
585
- }, harvestDelay)
586
- this.harvesterHandle.unref()
587
- }
588
-
589
626
  /**
590
627
  * `agent_enabled` changed. This will generally only happen because of a high
591
628
  * security mode mismatch between the agent and the collector. This only
@@ -715,7 +752,7 @@ Agent.prototype._addEventFromTransaction = function _addEventFromTransaction(tx)
715
752
  agentAttributes
716
753
  ]
717
754
 
718
- this.events.add(event, tx.priority || Math.random())
755
+ this.transactionEventAggregator.add(event, tx.priority || Math.random())
719
756
  }
720
757
 
721
758
  /**
@@ -734,8 +771,10 @@ Agent.prototype._transactionFinished = function _transactionFinished(transaction
734
771
  if (transaction.forceIgnore === false) {
735
772
  logger.debug('Explicitly not ignoring %s (%s).', transaction.name, transaction.id)
736
773
  }
737
- this.metrics.merge(transaction.metrics)
738
- this.errors.onTransactionFinished(transaction, this.metrics)
774
+ this.metrics.merge(transaction.metrics, false)
775
+
776
+ this.errors.onTransactionFinished(transaction)
777
+
739
778
  this.traces.add(transaction)
740
779
 
741
780
  const trace = transaction.trace
@@ -779,7 +818,6 @@ Agent.prototype.recordSupportability = function recordSupportability(name, value
779
818
 
780
819
  Agent.prototype._listenForConfigChanges = function _listenForConfigChanges() {
781
820
  const self = this
782
- this.config.on('apdex_t', this._apdexTChange.bind(this))
783
821
  this.config.on('agent_enabled', this._enabledChange.bind(this))
784
822
  this.config.on('change', this._configChange.bind(this))
785
823
  this.config.on('metric_name_rules', function updateMetricNameNormalizer() {
@@ -803,12 +841,47 @@ Agent.prototype._listenForConfigChanges = function _listenForConfigChanges() {
803
841
  self.transactionSampler.samplingPeriod = period * 1000
804
842
  }
805
843
  )
806
- this.config.on(
807
- 'transaction_events.max_samples_per_minute',
808
- function updateEventSampleLimit(maxSamples) {
809
- self.events.setLimit(maxSamples)
844
+ this.config.on('event_harvest_config', function onHarvestConfigReceived(harvestConfig) {
845
+ if (harvestConfig) {
846
+ generateEventHarvestSupportMetrics(self, harvestConfig)
810
847
  }
811
- )
848
+ })
849
+ }
850
+
851
+ function generateEventHarvestSupportMetrics(agent, harvestConfig) {
852
+ const harvestLimits = harvestConfig.harvest_limits
853
+
854
+ const harvestNames = NAMES.EVENT_HARVEST
855
+ const harvestLimitNames = harvestNames.HARVEST_LIMIT
856
+
857
+ const reportPeriodMetric = agent.metrics.getOrCreateMetric(harvestNames.REPORT_PERIOD)
858
+ reportPeriodMetric.recordValue(harvestConfig.report_period_ms)
859
+
860
+ const analyticLimit = harvestLimits.analytic_event_data
861
+ if (analyticLimit) {
862
+ const analyticLimitMetric = agent.metrics.getOrCreateMetric(
863
+ harvestLimitNames.ANALYTIC
864
+ )
865
+ analyticLimitMetric.recordValue(analyticLimit)
866
+ }
867
+
868
+ const customLimit = harvestLimits.custom_event_data
869
+ if (customLimit) {
870
+ const customLimitMetric = agent.metrics.getOrCreateMetric(harvestLimitNames.CUSTOM)
871
+ customLimitMetric.recordValue(customLimit)
872
+ }
873
+
874
+ const errorLimit = harvestLimits.error_event_data
875
+ if (errorLimit) {
876
+ const errorLimitMetric = agent.metrics.getOrCreateMetric(harvestLimitNames.ERROR)
877
+ errorLimitMetric.recordValue(errorLimit)
878
+ }
879
+
880
+ const spanLimit = harvestLimits.span_event_data
881
+ if (spanLimit) {
882
+ const spanLimitMetric = agent.metrics.getOrCreateMetric(harvestLimitNames.SPAN)
883
+ spanLimitMetric.recordValue(spanLimit)
884
+ }
812
885
  }
813
886
 
814
887
  module.exports = Agent