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.
- package/NEWS.md +222 -1
- package/api.js +24 -11
- package/bin/compare-bench-results.js +167 -0
- package/bin/run-bench.js +87 -44
- package/bin/travis-setup.sh +18 -7
- package/index.js +16 -8
- package/lib/collector/remote-method.js +12 -0
- package/lib/config/default.js +5 -0
- package/lib/config/env.js +2 -1
- package/lib/config/index.js +5 -0
- package/lib/db/tracer.js +17 -6
- package/lib/environment.js +1 -1
- package/lib/errors/index.js +1 -1
- package/lib/instrumentation/bluebird.js +35 -45
- package/lib/instrumentation/core/async_hooks.js +8 -8
- package/lib/instrumentation/core/child_process.js +41 -34
- package/lib/instrumentation/core/crypto.js +9 -14
- package/lib/instrumentation/core/dns.js +9 -13
- package/lib/instrumentation/core/domain.js +10 -12
- package/lib/instrumentation/core/fs.js +47 -38
- package/lib/instrumentation/core/globals.js +12 -17
- package/lib/instrumentation/core/http.js +3 -3
- package/lib/instrumentation/core/inspector.js +8 -7
- package/lib/instrumentation/core/net.js +62 -66
- package/lib/instrumentation/core/timers.js +79 -40
- package/lib/instrumentation/core/zlib.js +22 -8
- package/lib/instrumentation/mongodb.js +158 -115
- package/lib/instrumentations.js +3 -2
- package/lib/priority-queue.js +5 -2
- package/lib/shim/constants.js +6 -0
- package/lib/shim/datastore-shim.js +1 -1
- package/lib/shim/index.js +9 -5
- package/lib/shim/message-shim.js +5 -4
- package/lib/shim/promise-shim.js +563 -0
- package/lib/shim/shim.js +90 -11
- package/lib/shim/specs/index.js +8 -5
- package/lib/shimmer.js +70 -28
- package/lib/transaction/dt-payload.js +16 -0
- package/lib/transaction/handle.js +1 -1
- package/lib/transaction/index.js +22 -8
- package/lib/transaction/trace/aggregator.js +3 -3
- package/lib/transaction/trace/index.js +30 -37
- package/lib/util/codec.js +4 -1
- 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 (
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
1432
|
+
opts = new specs.SegmentSpec({name})
|
|
1368
1433
|
|
|
1369
|
-
if
|
|
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
|
*
|
package/lib/shim/specs/index.js
CHANGED
|
@@ -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
|
|
57
|
-
this.promise
|
|
58
|
-
this.callback
|
|
59
|
-
this.rowCallback
|
|
60
|
-
this.after
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
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() {
|
package/lib/transaction/index.js
CHANGED
|
@@ -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 || !
|
|
758
|
-
logger.
|
|
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.
|
|
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
|
|
902
|
-
logger.
|
|
903
|
-
'Invalid configuration for distributed trace payload
|
|
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
|
|
132
|
+
isOverThreshold = duration >= config.transaction_threshold * TO_MILLIS
|
|
133
133
|
} else {
|
|
134
|
-
isOverThreshold = duration
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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.
|
|
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": "
|
|
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",
|