newrelic 4.6.0 → 4.9.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.
Files changed (44) hide show
  1. package/NEWS.md +222 -1
  2. package/api.js +24 -11
  3. package/bin/compare-bench-results.js +167 -0
  4. package/bin/run-bench.js +87 -44
  5. package/bin/travis-setup.sh +18 -7
  6. package/index.js +16 -8
  7. package/lib/collector/remote-method.js +12 -0
  8. package/lib/config/default.js +5 -0
  9. package/lib/config/env.js +2 -1
  10. package/lib/config/index.js +5 -0
  11. package/lib/db/tracer.js +17 -6
  12. package/lib/environment.js +1 -1
  13. package/lib/errors/index.js +1 -1
  14. package/lib/instrumentation/bluebird.js +35 -45
  15. package/lib/instrumentation/core/async_hooks.js +8 -8
  16. package/lib/instrumentation/core/child_process.js +41 -34
  17. package/lib/instrumentation/core/crypto.js +9 -14
  18. package/lib/instrumentation/core/dns.js +9 -13
  19. package/lib/instrumentation/core/domain.js +10 -12
  20. package/lib/instrumentation/core/fs.js +47 -38
  21. package/lib/instrumentation/core/globals.js +12 -17
  22. package/lib/instrumentation/core/http.js +3 -3
  23. package/lib/instrumentation/core/inspector.js +8 -7
  24. package/lib/instrumentation/core/net.js +62 -66
  25. package/lib/instrumentation/core/timers.js +79 -40
  26. package/lib/instrumentation/core/zlib.js +22 -8
  27. package/lib/instrumentation/mongodb.js +158 -115
  28. package/lib/instrumentations.js +3 -2
  29. package/lib/priority-queue.js +5 -2
  30. package/lib/shim/constants.js +6 -0
  31. package/lib/shim/datastore-shim.js +1 -1
  32. package/lib/shim/index.js +9 -5
  33. package/lib/shim/message-shim.js +5 -4
  34. package/lib/shim/promise-shim.js +563 -0
  35. package/lib/shim/shim.js +90 -11
  36. package/lib/shim/specs/index.js +8 -5
  37. package/lib/shimmer.js +70 -28
  38. package/lib/transaction/dt-payload.js +16 -0
  39. package/lib/transaction/handle.js +1 -1
  40. package/lib/transaction/index.js +22 -8
  41. package/lib/transaction/trace/aggregator.js +3 -3
  42. package/lib/transaction/trace/index.js +30 -37
  43. package/lib/util/codec.js +4 -1
  44. package/package.json +10 -4
package/lib/shim/shim.js CHANGED
@@ -18,6 +18,10 @@ try {
18
18
  logger.debug(err, 'Failed to load es6 shimming methods.')
19
19
  }
20
20
 
21
+ // Some modules do terrible things, like change the prototype of functions. To
22
+ // avoid crashing things we'll use a cached copy of apply everywhere.
23
+ const fnApply = Function.prototype.apply
24
+
21
25
  /**
22
26
  * Constructs a shim associated with the given agent instance.
23
27
  *
@@ -105,6 +109,7 @@ Shim.prototype.unwrapOnce = unwrapOnce
105
109
  Shim.prototype.getOriginal = getOriginal
106
110
  Shim.prototype.getSegment = getSegment
107
111
  Shim.prototype.getActiveSegment = getActiveSegment
112
+ Shim.prototype.setActiveSegment = setActiveSegment
108
113
  Shim.prototype.storeSegment = storeSegment
109
114
  Shim.prototype.bindCallbackSegment = bindCallbackSegment
110
115
  Shim.prototype.applySegment = applySegment
@@ -117,6 +122,7 @@ Shim.prototype.isString = isString
117
122
  Shim.prototype.isNumber = isNumber
118
123
  Shim.prototype.isBoolean = isBoolean
119
124
  Shim.prototype.isArray = isArray
125
+ Shim.prototype.isNull = isNull
120
126
  Shim.prototype.toArray = toArray
121
127
  Shim.prototype.argsToArray = argsToArray
122
128
  Shim.prototype.normalizeIndex = normalizeIndex
@@ -490,6 +496,11 @@ Shim.prototype.__NR_unwrap = unwrapAll
490
496
  * If the function synchronously threw an error, that error will be handed to
491
497
  * this function.
492
498
  *
499
+ * @property {bool} [callbackRequired]
500
+ * When `true`, a recorded method must be called with a callback for a segment
501
+ * to be created. Does not apply if a custom callback method has been assigned
502
+ * via {@link callback}.
503
+ *
493
504
  * @see SegmentSpec
494
505
  * @see RecorderFunction
495
506
  */
@@ -586,7 +597,7 @@ function wrap(nodule, properties, spec, args) {
586
597
  spec = this.setDefaults(spec, {matchArity: false})
587
598
 
588
599
  // If we're just wrapping one thing, just wrap it and return.
589
- if (!properties) {
600
+ if (properties == null) {
590
601
  this.logger.trace('Wrapping nodule itself.')
591
602
  return _wrap(this, nodule, this.getName(nodule), spec, args)
592
603
  }
@@ -690,7 +701,7 @@ function wrapReturn(nodule, properties, spec, args) {
690
701
  fnArgs.unshift(fn) // `unshift` === `push_front`
691
702
  ctx = ret = new (fn.bind.apply(fn, fnArgs))()
692
703
  } else {
693
- ret = fn.apply(ctx, arguments)
704
+ ret = fnApply.call(fn, ctx, arguments)
694
705
  }
695
706
 
696
707
  // Assemble the arguments to hand to the spec.
@@ -883,7 +894,7 @@ function record(nodule, properties, recordNamer) {
883
894
  var segDesc = recordNamer.call(this, shim, fn, name, args)
884
895
  if (!segDesc) {
885
896
  shim.logger.trace('No segment descriptor for "%s", not recording.', name)
886
- return fn.apply(this, args)
897
+ return fnApply.call(fn, this, args)
887
898
  }
888
899
  segDesc = new specs.RecorderSpec(segDesc)
889
900
 
@@ -899,7 +910,14 @@ function record(nodule, properties, recordNamer) {
899
910
 
900
911
  if (!parent) {
901
912
  shim.logger.debug('Not recording function %s, not in a transaction.', name)
902
- return fn.apply(this, arguments)
913
+ return fnApply.call(fn, this, arguments)
914
+ }
915
+
916
+ if (
917
+ segDesc.callbackRequired &&
918
+ !_hasValidCallbackArg(shim, args, segDesc.callback)
919
+ ) {
920
+ return fnApply.call(fn, this, arguments)
903
921
  }
904
922
 
905
923
  // Only create a segment if:
@@ -913,6 +931,20 @@ function record(nodule, properties, recordNamer) {
913
931
  return _doRecord.call(this, segment, args, segDesc, shouldCreateSegment)
914
932
  }
915
933
 
934
+ function _hasValidCallbackArg(shim, args, specCallback) {
935
+ if (shim.isNumber(specCallback)) {
936
+ const cbIdx = normalizeIndex(args.length, specCallback)
937
+ if (cbIdx === null) {
938
+ return false
939
+ }
940
+
941
+ const callback = args[cbIdx]
942
+ return shim.isFunction(callback)
943
+ }
944
+
945
+ return true
946
+ }
947
+
916
948
  function _doRecord(segment, args, segDesc, shouldCreateSegment) {
917
949
  // Now bind any callbacks specified in the segment descriptor.
918
950
  _bindAllCallbacks.call(this, shim, fn, name, args, {
@@ -946,7 +978,7 @@ function record(nodule, properties, recordNamer) {
946
978
  var promised = false
947
979
  var ret
948
980
  try {
949
- ret = shim.applySegment(fn, segment, true, ctx, args)
981
+ ret = shim.applySegment(fn, segment, true, ctx, args, segDesc.inContext)
950
982
  if (segDesc.after && segDesc.promise && shim.isPromise(ret)) {
951
983
  promised = true
952
984
  return ret.then(function onThen(val) {
@@ -1266,6 +1298,22 @@ function getActiveSegment(obj) {
1266
1298
  return null
1267
1299
  }
1268
1300
 
1301
+ /**
1302
+ * Explicitly sets the active segment to the one passed in. This method
1303
+ * should only be used if there is no function to tie a segment's timing
1304
+ * to.
1305
+ *
1306
+ * - `setActiveSegment(segment)`
1307
+ *
1308
+ * @memberof Shim.prototype
1309
+ *
1310
+ * @param {TraceSegment} segment - The segment to set as the active segment.
1311
+ *
1312
+ */
1313
+ function setActiveSegment(segment) {
1314
+ return this.tracer.segment = segment
1315
+ }
1316
+
1269
1317
  /**
1270
1318
  * Associates a segment with the given object.
1271
1319
  *
@@ -1286,7 +1334,7 @@ function storeSegment(obj, segment) {
1286
1334
  * Sets the given segment as the active one for the duration of the function's
1287
1335
  * execution.
1288
1336
  *
1289
- * - `applySegment(func, segment, full, context, args)`
1337
+ * - `applySegment(func, segment, full, context, args[, inContextCB])`
1290
1338
  *
1291
1339
  * @memberof Shim.prototype
1292
1340
  *
@@ -1305,9 +1353,15 @@ function storeSegment(obj, segment) {
1305
1353
  * @param {Array.<*>} args
1306
1354
  * The arguments to be passed into the function.
1307
1355
  *
1356
+ * @param {Function} [inContextCB]
1357
+ * The function used to do more instrumentation work. This function is
1358
+ * guaranteed to be executed with the segment associated with.
1359
+ *
1360
+ *
1308
1361
  * @return {*} Whatever value `func` returned.
1309
1362
  */
1310
- function applySegment(func, segment, full, context, args) {
1363
+ /* eslint-disable max-params */
1364
+ function applySegment(func, segment, full, context, args, inContextCB) {
1311
1365
  // Exist fast for bad arguments.
1312
1366
  if (!this.isFunction(func)) {
1313
1367
  return
@@ -1315,7 +1369,7 @@ function applySegment(func, segment, full, context, args) {
1315
1369
 
1316
1370
  if (!segment) {
1317
1371
  this.logger.trace('No segment to apply to function.')
1318
- return func.apply(context, args)
1372
+ return fnApply.call(func, context, args)
1319
1373
  }
1320
1374
  this.logger.trace('Applying segment %s', segment.name)
1321
1375
 
@@ -1327,9 +1381,19 @@ function applySegment(func, segment, full, context, args) {
1327
1381
  segment.start()
1328
1382
  }
1329
1383
 
1384
+ if (typeof inContextCB === 'function') {
1385
+ inContextCB()
1386
+ }
1387
+
1330
1388
  // Execute the function and then return the tracer segment to the old one.
1331
1389
  try {
1332
- return func.apply(context, args)
1390
+ return fnApply.call(func, context, args)
1391
+ } catch (error) {
1392
+ if (prevSegment === null && process.domain != null) {
1393
+ process.domain.__NR_segment = tracer.segment
1394
+ }
1395
+
1396
+ throw error // Rethrowing application error, this is not an agent error.
1333
1397
  } finally {
1334
1398
  if (full) {
1335
1399
  segment.touch()
@@ -1337,6 +1401,7 @@ function applySegment(func, segment, full, context, args) {
1337
1401
  tracer.segment = prevSegment
1338
1402
  }
1339
1403
  }
1404
+ /* eslint-enable max-params */
1340
1405
 
1341
1406
  /**
1342
1407
  * Creates a new segment.
@@ -1364,9 +1429,10 @@ function createSegment(name, recorder, parent) {
1364
1429
  var opts = null
1365
1430
  if (this.isString(name)) {
1366
1431
  // createSegment(name [, recorder] [, parent])
1367
- opts = new specs.SegmentSpec({name: name})
1432
+ opts = new specs.SegmentSpec({name})
1368
1433
 
1369
- if (this.isFunction(recorder)) {
1434
+ // if the recorder arg is not used, it can either be omitted or null
1435
+ if (this.isFunction(recorder) || this.isNull(recorder)) {
1370
1436
  // createSegment(name, recorder [, parent])
1371
1437
  opts.recorder = recorder
1372
1438
  opts.parent = parent
@@ -1504,6 +1570,19 @@ function isPromise(obj) {
1504
1570
  return obj && typeof obj.then === 'function'
1505
1571
  }
1506
1572
 
1573
+ /**
1574
+ * Determines if the given value is null.
1575
+ *
1576
+ * @memberof Shim.prototype
1577
+ *
1578
+ * @param {*} val - The value to check.
1579
+ *
1580
+ * @return {bool} True if the value is null, else false.
1581
+ */
1582
+ function isNull(val) {
1583
+ return val === null
1584
+ }
1585
+
1507
1586
  /**
1508
1587
  * Converts an array-like object into an array.
1509
1588
  *
@@ -45,6 +45,7 @@ function WrapSpec(spec) {
45
45
  function SegmentSpec(spec) {
46
46
  this.name = hasOwnProperty(spec, 'name') ? spec.name : null
47
47
  this.recorder = hasOwnProperty(spec, 'recorder') ? spec.recorder : null
48
+ this.inContext = hasOwnProperty(spec, 'inContext') ? spec.inContext : null
48
49
  this.parent = hasOwnProperty(spec, 'parent') ? spec.parent : null
49
50
  this.parameters = hasOwnProperty(spec, 'parameters') ? spec.parameters : null
50
51
  this.internal = hasOwnProperty(spec, 'internal') ? spec.internal : false
@@ -53,11 +54,13 @@ function SegmentSpec(spec) {
53
54
 
54
55
  function RecorderSpec(spec) {
55
56
  SegmentSpec.call(this, spec)
56
- this.stream = hasOwnProperty(spec, 'stream') ? spec.stream : null
57
- this.promise = hasOwnProperty(spec, 'promise') ? spec.promise : null
58
- this.callback = hasOwnProperty(spec, 'callback') ? spec.callback : null
59
- this.rowCallback = hasOwnProperty(spec, 'rowCallback') ? spec.rowCallback : null
60
- this.after = hasOwnProperty(spec, 'after') ? spec.after : null
57
+ this.stream = hasOwnProperty(spec, 'stream') ? spec.stream : null
58
+ this.promise = hasOwnProperty(spec, 'promise') ? spec.promise : null
59
+ this.callback = hasOwnProperty(spec, 'callback') ? spec.callback : null
60
+ this.rowCallback = hasOwnProperty(spec, 'rowCallback') ? spec.rowCallback : null
61
+ this.after = hasOwnProperty(spec, 'after') ? spec.after : null
62
+ this.callbackRequired = hasOwnProperty(spec, 'callbackRequired') ?
63
+ spec.callbackRequired : null
61
64
  }
62
65
  util.inherits(RecorderSpec, SegmentSpec)
63
66
 
package/lib/shimmer.js CHANGED
@@ -7,29 +7,63 @@ var INSTRUMENTATIONS = require('./instrumentations')()
7
7
  var properties = require('./util/properties')
8
8
  var shims = require('./shim')
9
9
 
10
-
11
- var CORE_INSTRUMENTATION = {
12
- child_process: 'child_process.js',
13
- crypto: 'crypto.js',
14
- // domain: 'domain.js', // XXX Do not include domains in this list! The core
15
- dns: 'dns.js', // instrumentations are run at startup by requiring
16
- fs: 'fs.js', // each of their modules. Loading `domain` has side
17
- http: 'http.js', // effects that we should try to avoid.
18
- https: 'http.js',
19
- inspector: 'inspector.js',
20
- net: 'net.js',
21
- timers: 'timers.js',
22
- zlib: 'zlib.js'
23
- }
24
-
25
10
  var MODULE_TYPE = shims.constants.MODULE_TYPE
26
11
 
27
12
  var SHIM_TYPE_MAP = Object.create(null)
28
13
  SHIM_TYPE_MAP[MODULE_TYPE.GENERIC] = shims.Shim
29
14
  SHIM_TYPE_MAP[MODULE_TYPE.DATASTORE] = shims.DatastoreShim
30
15
  SHIM_TYPE_MAP[MODULE_TYPE.MESSAGE] = shims.MessageShim
16
+ SHIM_TYPE_MAP[MODULE_TYPE.PROMISE] = shims.PromiseShim
17
+ SHIM_TYPE_MAP[MODULE_TYPE.TRANSACTION] = shims.TransactionShim
31
18
  SHIM_TYPE_MAP[MODULE_TYPE.WEB_FRAMEWORK] = shims.WebFrameworkShim
32
19
 
20
+ var CORE_INSTRUMENTATION = {
21
+ child_process: {
22
+ type: MODULE_TYPE.GENERIC,
23
+ file: 'child_process.js'
24
+ },
25
+ crypto: {
26
+ type: MODULE_TYPE.GENERIC,
27
+ file: 'crypto.js'
28
+ },
29
+ // domain: { // XXX Do not include domains in this list! The
30
+ // type: MODULE_TYPE.GENERIC, // core instrumentations are run at startup by
31
+ // file: 'domain.js' // requiring each of their modules. Loading
32
+ // }, // `domain` has side effects that we try to avoid.
33
+ dns: {
34
+ type: MODULE_TYPE.GENERIC,
35
+ file: 'dns.js'
36
+ },
37
+ fs: {
38
+ type: MODULE_TYPE.GENERIC,
39
+ file: 'fs.js'
40
+ },
41
+ http: {
42
+ type: MODULE_TYPE.TRANSACTION,
43
+ file: 'http.js'
44
+ },
45
+ https: {
46
+ type: MODULE_TYPE.TRANSACTION,
47
+ file: 'http.js'
48
+ },
49
+ inspector: {
50
+ type: MODULE_TYPE.GENERIC,
51
+ file: 'inspector.js'
52
+ },
53
+ net: {
54
+ type: MODULE_TYPE.GENERIC,
55
+ file: 'net.js'
56
+ },
57
+ timers: {
58
+ type: MODULE_TYPE.GENERIC,
59
+ file: 'timers.js'
60
+ },
61
+ zlib: {
62
+ type: MODULE_TYPE.GENERIC,
63
+ file: 'zlib.js'
64
+ }
65
+ }
66
+
33
67
  /**
34
68
  * Unwrapping is only likely to be used by test code, and is a fairly drastic
35
69
  * maneuver, but it should be pretty safe if there's a desire to reboot the
@@ -40,6 +74,17 @@ SHIM_TYPE_MAP[MODULE_TYPE.WEB_FRAMEWORK] = shims.WebFrameworkShim
40
74
  */
41
75
  var instrumented = []
42
76
 
77
+ function applyDebugState(shimmer, shim, nodule) {
78
+ if (shimmer.debug) {
79
+ shim.enableDebug()
80
+ instrumented.push(shim)
81
+ instrumented.push({__NR_unwrap: function unwrapNodule() {
82
+ delete nodule.__NR_instrumented
83
+ }})
84
+ nodule.__NR_shim = shim
85
+ }
86
+ }
87
+
43
88
  /**
44
89
  * All instrumentation files must export the same interface: a single
45
90
  * initialization function that takes the agent and the module to be
@@ -63,14 +108,7 @@ function instrument(agent, nodule, moduleName, resolvedName) {
63
108
  shim = new shims.Shim(agent, moduleName, resolvedName)
64
109
  }
65
110
 
66
- if (shimmer.debug) {
67
- shim.enableDebug()
68
- instrumented.push(shim)
69
- instrumented.push({__NR_unwrap: function unwrapNodule() {
70
- delete nodule.__NR_instrumented
71
- }})
72
- nodule.__NR_shim = shim
73
- }
111
+ applyDebugState(shimmer, shim, nodule)
74
112
 
75
113
  try {
76
114
  if (instrumentation.onRequire(shim, nodule, moduleName) !== false) {
@@ -339,14 +377,16 @@ var shimmer = module.exports = {
339
377
 
340
378
  bootstrapInstrumentation: function bootstrapInstrumentation(agent) {
341
379
  // Instrument global.
380
+ const globalShim = new shims.Shim(agent, 'globals', 'globals')
381
+ applyDebugState(shimmer, globalShim, global)
342
382
  var globalsFilepath = path.join(__dirname, 'instrumentation', 'core', 'globals.js')
343
- _firstPartyInstrumentation(agent, globalsFilepath, shimmer, global, 'globals')
383
+ _firstPartyInstrumentation(agent, globalsFilepath, globalShim, global, 'globals')
344
384
 
345
385
  // Instrument each of the core modules.
346
386
  Object.keys(CORE_INSTRUMENTATION).forEach(function forEachCore(mojule) {
347
- var fileName = CORE_INSTRUMENTATION[mojule]
348
- var filePath = path.join(__dirname, 'instrumentation', 'core', fileName)
349
- var uninstrumented
387
+ const core = CORE_INSTRUMENTATION[mojule]
388
+ const filePath = path.join(__dirname, 'instrumentation', 'core', core.file)
389
+ let uninstrumented = null
350
390
 
351
391
  try {
352
392
  uninstrumented = require(mojule)
@@ -358,7 +398,9 @@ var shimmer = module.exports = {
358
398
  )
359
399
  }
360
400
 
361
- var shim = new shims.Shim(agent, mojule, mojule)
401
+ const ShimClass = SHIM_TYPE_MAP[core.type]
402
+ const shim = new ShimClass(agent, mojule, mojule)
403
+ applyDebugState(shimmer, shim, core)
362
404
  _firstPartyInstrumentation(agent, filePath, shim, uninstrumented, mojule)
363
405
  })
364
406
 
@@ -7,6 +7,12 @@ const DT_VERSION_MAJOR = 0
7
7
  const DT_VERSION_MINOR = 1
8
8
 
9
9
  module.exports = class DistributedTracePayload {
10
+ /**
11
+ * The class reponsible for producing distributed trace payloads.
12
+ * Created by calling {@link TransactionHandle#createDistributedTracePayload}.
13
+ *
14
+ * @constructor
15
+ */
10
16
  constructor(payload) {
11
17
  logger.trace('DistributedTracePayload created with %s', payload)
12
18
  this.plainTextPayload = JSON.stringify({
@@ -16,11 +22,21 @@ module.exports = class DistributedTracePayload {
16
22
  this.base64Payload = null
17
23
  }
18
24
 
25
+ /**
26
+ * @returns {String} The base64 encoded JSON representation of the
27
+ * distributed trace payload.
28
+ */
19
29
  text() {
20
30
  logger.trace('DistributedTracePayload text: %s', this.plainTextPayload)
21
31
  return this.plainTextPayload
22
32
  }
23
33
 
34
+ /**
35
+ * Construct a payload suitable for HTTP transport.
36
+ *
37
+ * @returns {String} The base64 encoded JSON representation of the
38
+ * distributed trace payload.
39
+ */
24
40
  httpSafe() {
25
41
  if (!this.base64Payload) {
26
42
  this.base64Payload = makeBuffer(this.plainTextPayload, 'utf-8').toString('base64')
@@ -33,7 +33,7 @@ module.exports = class TransactionHandle {
33
33
  *
34
34
  * Proxy method for Transaction#createDistrubtedTracePayload.
35
35
  *
36
- * @returns {DistributedTracePayload} The created payload.
36
+ * @returns {DistributedTracePayload} The created payload object.
37
37
  *
38
38
  */
39
39
  createDistributedTracePayload() {
@@ -193,6 +193,10 @@ Transaction.prototype.end = function end(done) {
193
193
  transaction.record()
194
194
  }
195
195
 
196
+ // This is function currently must be called after all recorders have been fired due
197
+ // to some of the recorders (namely the db recorders) add parameters to the segments.
198
+ transaction.trace.generateSpanEvents()
199
+
196
200
  transaction.agent.emit('transactionFinished', transaction)
197
201
  if (typeof done === 'function') {
198
202
  done(transaction)
@@ -751,11 +755,16 @@ function acceptDistributedTracePayload(payload, transport) {
751
755
 
752
756
  const config = this.agent.config
753
757
  const distTraceEnabled = config.distributed_tracing.enabled
754
- const catEnabled = config.cross_application_tracer.enabled
755
758
  const trustedAccount = config.trusted_account_key || config.account_id
756
759
 
757
- if (!distTraceEnabled || !catEnabled || !trustedAccount) {
758
- logger.warn('Invalid configuration for distributed trace payload, not accepting')
760
+ if (!distTraceEnabled || !trustedAccount) {
761
+ logger.debug(
762
+ 'Invalid configuration for distributed trace payload, not accepting ' +
763
+ '(distributed_tracing.enabled: %s, trustKey: %s',
764
+ distTraceEnabled,
765
+ trustedAccount
766
+ )
767
+
759
768
  this.agent.recordSupportability('DistributedTrace/AcceptPayload/Exception')
760
769
  return
761
770
  }
@@ -894,15 +903,20 @@ Transaction.prototype.createDistributedTracePayload = createDistributedTracePayl
894
903
  function createDistributedTracePayload() {
895
904
  const config = this.agent.config
896
905
  const accountId = config.account_id
897
- const appId = config.application_id
906
+ const appId = config.primary_application_id
898
907
  const distTraceEnabled = config.distributed_tracing.enabled
899
- const catEnabled = config.cross_application_tracer.enabled
900
908
 
901
- if (!accountId || !appId || !distTraceEnabled || !catEnabled) {
902
- logger.warn(
903
- 'Invalid configuration for distributed trace payload in transaction %s',
909
+ if (!accountId || !appId || !distTraceEnabled) {
910
+ logger.debug(
911
+ 'Invalid configuration for distributed trace payload ' +
912
+ '(distributed_tracing.enabled: %s, account_id: %s, application_id: %s) ' +
913
+ 'in transaction %s',
914
+ distTraceEnabled,
915
+ accountId,
916
+ appId,
904
917
  this.id
905
918
  )
919
+
906
920
  return new DTPayloadStub()
907
921
  }
908
922
 
@@ -126,12 +126,12 @@ TraceAggregator.prototype.isBetter = function isBetter(name, duration, apdexT) {
126
126
  var isOverThreshold
127
127
 
128
128
  if (config &&
129
- config.transaction_threshold &&
129
+ config.transaction_threshold != null &&
130
130
  config.transaction_threshold !== 'apdex_f' &&
131
131
  typeof config.transaction_threshold === 'number') {
132
- isOverThreshold = duration > config.transaction_threshold * TO_MILLIS
132
+ isOverThreshold = duration >= config.transaction_threshold * TO_MILLIS
133
133
  } else {
134
- isOverThreshold = duration > 4 * TO_MILLIS * apdexT
134
+ isOverThreshold = duration >= 4 * TO_MILLIS * apdexT
135
135
  }
136
136
  if (!isOverThreshold) return false
137
137
 
@@ -49,30 +49,48 @@ function Trace(transaction) {
49
49
  * segments that support recording.
50
50
  */
51
51
  Trace.prototype.end = function end() {
52
+ var segments = [this.root]
53
+
54
+ while (segments.length) {
55
+ var segment = segments.pop()
56
+ _softEndSegment(segment)
57
+ Array.prototype.push.apply(segments, segment.getChildren())
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Iterates over the trace tree and generates a span event for each segment.
63
+ */
64
+ Trace.prototype.generateSpanEvents = function generateSpanEvents() {
52
65
  var config = this.transaction.agent.config
53
- var segments = []
54
- var processFn
55
66
 
56
67
  if (
57
68
  this.transaction.sampled &&
58
69
  config.span_events.enabled &&
59
70
  config.distributed_tracing.enabled
60
71
  ) {
72
+ var toProcess = []
61
73
  // Root segment does not become a span, so we need to process it separately.
62
- _softEndSegment(this.root)
74
+ const spanAggregator = this.transaction.agent.spans
63
75
  const children = this.root.getChildren()
64
76
  for (let i = 0; i < children.length; ++i) {
65
- segments.push(new DTTraceNode(children[i], this.transaction.parentSpanId))
77
+ toProcess.push(new DTTraceNode(children[i], this.transaction.parentSpanId))
66
78
  }
67
- processFn = makeDTNodeProcessor(this.transaction.agent.spans)
68
- } else {
69
- segments.push(this.root)
70
- processFn = processNode
71
- }
72
79
 
73
- while (segments.length) {
74
- var segment = segments.pop()
75
- processFn(segment, segments)
80
+ while (toProcess.length) {
81
+ var segmentInfo = toProcess.pop()
82
+ var segment = segmentInfo.segment
83
+
84
+ // Even though at some point we might want to stop adding events
85
+ // because all the priorities should be the same, we need to count
86
+ // the spans as seen.
87
+ spanAggregator.addSegment(segment, segmentInfo.parentId)
88
+
89
+ Array.prototype.push.apply(
90
+ toProcess,
91
+ segment.getChildren().map(child => new DTTraceNode(child, segment.id))
92
+ )
93
+ }
76
94
  }
77
95
  }
78
96
 
@@ -90,31 +108,6 @@ function _softEndSegment(segment) {
90
108
  }
91
109
  }
92
110
 
93
- function makeDTNodeProcessor(spanAggregator) {
94
- var addMoreSpans = true
95
- return function processNodeDT(segmentInfo, segments) {
96
- var segment = segmentInfo.segment
97
- _softEndSegment(segment)
98
-
99
- // This assumes that spans will have their transaction's priority. If insertion of
100
- // one event fails, all following will fail due to having the same priority.
101
- if (addMoreSpans && segment.name !== 'ROOT') {
102
- addMoreSpans = spanAggregator.addSegment(segment, segmentInfo.parentId)
103
- }
104
-
105
- var children = segment.getChildren()
106
- Array.prototype.push.apply(
107
- segments,
108
- children.map(child => new DTTraceNode(child, segment.id))
109
- )
110
- }
111
- }
112
-
113
- function processNode(segment, segments) {
114
- _softEndSegment(segment)
115
- Array.prototype.push.apply(segments, segment.getChildren())
116
- }
117
-
118
111
  /**
119
112
  * Add a child to the list of segments.
120
113
  *
package/lib/util/codec.js CHANGED
@@ -45,11 +45,14 @@ module.exports = {
45
45
  zlib.inflate(new Buffer(encoded, 'base64'), function cb_inflate(err, raw) {
46
46
  if (err) return callback(err)
47
47
 
48
+ let json
48
49
  try {
49
- return callback(null, JSON.parse(raw))
50
+ json = JSON.parse(raw)
50
51
  } catch (error) {
51
52
  return callback(error)
52
53
  }
54
+
55
+ return callback(null, json)
53
56
  })
54
57
  }
55
58
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "4.6.0",
3
+ "version": "4.9.0",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "licenses": [
6
6
  {
@@ -83,6 +83,11 @@
83
83
  "name": "Peter Svetlichny",
84
84
  "email": "psvetlichny@newrelic.com",
85
85
  "web": "https://newrelic.com"
86
+ },
87
+ {
88
+ "name": "Michael Goin",
89
+ "email": "mgoin@newrelic.com",
90
+ "web": "https://newrelic.com"
86
91
  }
87
92
  ],
88
93
  "description": "New Relic agent",
@@ -96,7 +101,7 @@
96
101
  ],
97
102
  "homepage": "http://github.com/newrelic/node-newrelic",
98
103
  "engines": {
99
- "node": ">=4.0.0",
104
+ "node": ">=4.0.0 <11.0.0",
100
105
  "npm": ">=2.0.0"
101
106
  },
102
107
  "directories": {
@@ -105,6 +110,7 @@
105
110
  "scripts": {
106
111
  "bench": "node ./bin/run-bench.js",
107
112
  "versioned-tests": "./bin/run-versioned-tests.sh",
113
+ "versioned": "./bin/run-versioned-tests.sh",
108
114
  "test": "make test"
109
115
  },
110
116
  "bin": {
@@ -112,6 +118,7 @@
112
118
  },
113
119
  "dependencies": {
114
120
  "@newrelic/koa": "^1.0.0",
121
+ "@newrelic/superagent": "^1.0.0",
115
122
  "@tyriar/fibonacci-heap": "^2.0.7",
116
123
  "async": "^2.1.4",
117
124
  "concat-stream": "^1.5.0",
@@ -132,13 +139,12 @@
132
139
  "chai": "^4.1.2",
133
140
  "connect": "*",
134
141
  "cover": "*",
135
- "ejs": "0.8.5",
142
+ "ejs": "2.5.5",
136
143
  "eslint": "^4.19.1",
137
144
  "express": "*",
138
145
  "generic-pool": "*",
139
146
  "glob": "^7.1.2",
140
147
  "got": "^8.0.1",
141
- "ioredis": "*",
142
148
  "jsdoc": "^3.4.0",
143
149
  "lodash": "^4.17.5",
144
150
  "memcached": ">=0.2.8",