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
@@ -0,0 +1,563 @@
1
+ 'use strict'
2
+
3
+ const logger = require('../logger').child({component: 'PromiseShim'})
4
+ const Shim = require('./shim')
5
+
6
+ /**
7
+ * A helper class for wrapping promise modules.
8
+ *
9
+ * @extends Shim
10
+ */
11
+ class PromiseShim extends Shim {
12
+ /**
13
+ * Constructs a shim associated with the given agent instance, specialized for
14
+ * instrumenting promise libraries.
15
+ *
16
+ * @param {Agent} agent
17
+ * The agent this shim will use.
18
+ *
19
+ * @param {string} moduleName
20
+ * The name of the module being instrumented.
21
+ *
22
+ * @param {string} resolvedName
23
+ * The full path to the loaded module.
24
+ *
25
+ * @see Shim
26
+ */
27
+ constructor(agent, moduleName, resolvedName) {
28
+ super(agent, moduleName, resolvedName)
29
+ this._logger = logger.child({module: moduleName})
30
+ this._class = null
31
+ }
32
+
33
+ /**
34
+ * Grants access to the `Contextualizer` class used by the `PromiseShim` to
35
+ * propagate context down promise chains.
36
+ *
37
+ * @private
38
+ */
39
+ static get Contextualizer() {
40
+ return Contextualizer
41
+ }
42
+
43
+ /**
44
+ * Sets the class used to indentify promises from the wrapped promise library.
45
+ *
46
+ * @param {function} clss - The promise library's class.
47
+ */
48
+ setClass(clss) {
49
+ this._class = clss
50
+ }
51
+
52
+ /**
53
+ * Checks if the given object is an instance of a promise from the promise
54
+ * library being wrapped.
55
+ *
56
+ * @param {*} obj - The object to check the instance type of.
57
+ *
58
+ * @return {bool} True if the provided object is an instance of a promise from
59
+ * this promise library.
60
+ *
61
+ * @see PromiseShim#setClass
62
+ */
63
+ isPromiseInstance(obj) {
64
+ return !!this._class && obj instanceof this._class
65
+ }
66
+
67
+ /**
68
+ * Wraps the given properties as constructors for the promise library.
69
+ *
70
+ * - `wrapConstructor(nodule, properties)`
71
+ * - `wrapConstructor(func)`
72
+ *
73
+ * It is only necessary to wrap the constructor for the class if there is no
74
+ * other way to access the executor function. Some libraries expose a separate
75
+ * method which is called to execute the executor. If that is available, it is
76
+ * better to wrap that using {@link PromiseShim#wrapExecutorCaller} than to
77
+ * use this method.
78
+ *
79
+ * @param {object|function} nodule
80
+ * The source of the properties to wrap, or a single function to wrap.
81
+ *
82
+ * @param {string|array.<string>} [properties]
83
+ * One or more properties to wrap. If omitted, the `nodule` parameter is
84
+ * assumed to be the constructor to wrap.
85
+ *
86
+ * @return {object|function} The first parameter to this function, after
87
+ * wrapping it or its properties.
88
+ *
89
+ * @see PromiseShim#wrapExecutorCaller
90
+ */
91
+ wrapConstructor(nodule, properties) {
92
+ return this.wrapClass(nodule, properties, {
93
+ pre: function prePromise(shim, Promise, name, args) {
94
+ // We are expecting one function argument for executor, anything else is
95
+ // non-standard, do not attempt to wrap. Also do not attempt to wrap if
96
+ // we are not in a transaction.
97
+ if (args.length !== 1 || !shim.isFunction(args[0]) || !shim.getActiveSegment()) {
98
+ return
99
+ }
100
+ _wrapExecutorContext(shim, args)
101
+ },
102
+ post: function postPromise(shim, Promise, name, args) {
103
+ // This extra property is added by `_wrapExecutorContext` in the pre step.
104
+ const executor = args[0]
105
+ const context = executor && executor.__NR_executorContext
106
+ if (!context || !shim.isFunction(context.executor)) {
107
+ return
108
+ }
109
+
110
+ context.promise = this
111
+ Contextualizer.link(null, this, shim.getSegment())
112
+ try {
113
+ // Must run after promise is defined so that `__NR_wrapper` can be set.
114
+ context.executor.apply(context.self, context.args)
115
+ } catch (e) {
116
+ const reject = context.args[1]
117
+ reject(e)
118
+ }
119
+ }
120
+ })
121
+ }
122
+
123
+ /**
124
+ * Wraps the given properties as the caller of promise executors.
125
+ *
126
+ * - `wrapExecutorCaller(nodule, properties)`
127
+ * - `wrapExecutorCaller(func)`
128
+ *
129
+ * Wrapping the executor caller method directly is preferable to wrapping
130
+ * the constructor of the promise class.
131
+ *
132
+ * @param {object|function} nodule
133
+ * The source of the properties to wrap, or a single function to wrap.
134
+ *
135
+ * @param {string|array.<string>} [properties]
136
+ * One or more properties to wrap. If omitted, the `nodule` parameter is
137
+ * assumed to be the function to wrap.
138
+ *
139
+ * @return {object|function} The first parameter to this function, after
140
+ * wrapping it or its properties.
141
+ *
142
+ * @see PromiseShim#wrapConstructor
143
+ */
144
+ wrapExecutorCaller(nodule, properties) {
145
+ return this.wrap(nodule, properties, function executorWrapper(shim, caller) {
146
+ if (!shim.isFunction(caller) || shim.isWrapped(caller)) {
147
+ return
148
+ }
149
+
150
+ return function wrappedExecutorCaller(executor) {
151
+ var parent = shim.getActiveSegment()
152
+ if (!this || !parent) {
153
+ return caller.apply(this, arguments)
154
+ }
155
+
156
+ if (!this.__NR_context) {
157
+ Contextualizer.link(null, this, parent)
158
+ }
159
+
160
+ const args = shim.argsToArray.apply(shim, arguments)
161
+ _wrapExecutorContext(shim, args)
162
+ const ret = caller.apply(this, args)
163
+ const context = args[0].__NR_executorContext
164
+ context.promise = this
165
+
166
+ // Bluebird catches executor errors and auto-rejects when it catches them,
167
+ // thus we need to do so as well.
168
+ //
169
+ // When adding new libraries, make sure to check that they behave the same
170
+ // way. We may need to enhance the promise spec to handle this variance.
171
+ try {
172
+ executor.apply(context.self, context.args)
173
+ } catch (e) {
174
+ const reject = context.args[1]
175
+ reject(e)
176
+ }
177
+ return ret
178
+ }
179
+ })
180
+ }
181
+
182
+ /**
183
+ * Wraps the given properties as methods which take is some value other than
184
+ * a function to call and return a promise.
185
+ *
186
+ * - `wrapCast(nodule, properties)`
187
+ * - `wrapCast(func)`
188
+ *
189
+ * Examples of promise cast methods include `Promise.resolve`, `Promise.all`,
190
+ * and Bluebird's `Promise.delay`. These are static methods which accept some
191
+ * arbitrary value and return a Promise instance.
192
+ *
193
+ * @param {object|function} nodule
194
+ * The source of the properties to wrap, or a single function to wrap.
195
+ *
196
+ * @param {string|array.<string>} [properties]
197
+ * One or more properties to wrap. If omitted, the `nodule` parameter is
198
+ * assumed to be the function to wrap.
199
+ *
200
+ * @return {object|function} The first parameter to this function, after
201
+ * wrapping it or its properties.
202
+ */
203
+ wrapCast(nodule, properties) {
204
+ return this.wrap(nodule, properties, function castWrapper(shim, cast) {
205
+ if (!shim.isFunction(cast) || shim.isWrapped(cast)) {
206
+ return
207
+ }
208
+
209
+ return function __NR_wrappedCast() {
210
+ const segment = shim.getSegment()
211
+ const prom = cast.apply(this, arguments)
212
+ if (segment) {
213
+ Contextualizer.link(null, prom, segment)
214
+ }
215
+ return prom
216
+ }
217
+ })
218
+ }
219
+
220
+ /**
221
+ * Wraps the given properties as promise chaining methods.
222
+ *
223
+ * - `wrapThen(nodule, properties)`
224
+ * - `wrapThen(func)`
225
+ *
226
+ * NOTE: You must set class used by the library before wrapping then-methods.
227
+ *
228
+ * Examples of promise then methods include `Promise#then`, `Promise#finally`,
229
+ * and Bluebird's `Promise#map`. These are methods which take a function to
230
+ * execute once the promise resolves and hands back a new promise.
231
+ *
232
+ * @param {object|function} nodule
233
+ * The source of the properties to wrap, or a single function to wrap.
234
+ *
235
+ * @param {string|array.<string>} [properties]
236
+ * One or more properties to wrap. If omitted, the `nodule` parameter is
237
+ * assumed to be the function to wrap.
238
+ *
239
+ * @return {object|function} The first parameter to this function, after
240
+ * wrapping it or its properties.
241
+ *
242
+ * @see PromiseShim#setClass
243
+ * @see PromiseShim#wrapCatch
244
+ */
245
+ wrapThen(nodule, properties) {
246
+ return this.wrap(nodule, properties, _wrapThen, [true])
247
+ }
248
+
249
+ /**
250
+ * Wraps the given properties as rejected promise chaining methods.
251
+ *
252
+ * - `wrapCatch(nodule, properties)`
253
+ * - `wrapCatch(func)`
254
+ *
255
+ * NOTE: You must set class used by the library before wrapping catch-methods.
256
+ *
257
+ * Promise catch methods differ from then methods in that only one function
258
+ * will be executed and only if the promise is rejected. Some libraries accept
259
+ * an additional argument to `Promise#catch` which is usually an error class
260
+ * to filter rejections by. This wrap method will handle that case.
261
+ *
262
+ * @param {object|function} nodule
263
+ * The source of the properties to wrap, or a single function to wrap.
264
+ *
265
+ * @param {string|array.<string>} [properties]
266
+ * One or more properties to wrap. If omitted, the `nodule` parameter is
267
+ * assumed to be the function to wrap.
268
+ *
269
+ * @return {object|function} The first parameter to this function, after
270
+ * wrapping it or its properties.
271
+ *
272
+ * @see PromiseShim#setClass
273
+ * @see PromiseShim#wrapThen
274
+ */
275
+ wrapCatch(nodule, properties) {
276
+ return this.wrap(nodule, properties, _wrapThen, [false])
277
+ }
278
+
279
+ /**
280
+ * Wraps the given properties as callback-to-promise conversion methods.
281
+ *
282
+ * - `wrapPromisify(nodule, properties)`
283
+ * - `wrapPromisify(func)`
284
+ *
285
+ * @param {object|function} nodule
286
+ * The source of the properties to wrap, or a single function to wrap.
287
+ *
288
+ * @param {string|array.<string>} [properties]
289
+ * One or more properties to wrap. If omitted, the `nodule` parameter is
290
+ * assumed to be the function to wrap.
291
+ *
292
+ * @return {object|function} The first parameter to this function, after
293
+ * wrapping it or its properties.
294
+ */
295
+ wrapPromisify(nodule, properties) {
296
+ return this.wrap(nodule, properties, function promisifyWrapper(shim, promisify) {
297
+ if (!shim.isFunction(promisify) || shim.isWrapped(promisify)) {
298
+ return
299
+ }
300
+
301
+ return function __NR_wrappedPromisify() {
302
+ const promisified = promisify.apply(this, arguments)
303
+ if (typeof promisified !== 'function') {
304
+ return promisified
305
+ }
306
+
307
+ Object.keys(promisified).forEach(function forEachProperty(prop) {
308
+ __NR_wrappedPromisified[prop] = promisified[prop]
309
+ })
310
+
311
+ return __NR_wrappedPromisified
312
+ function __NR_wrappedPromisified() {
313
+ const segment = shim.getActiveSegment()
314
+ if (!segment) {
315
+ return promisified.apply(this, arguments)
316
+ }
317
+
318
+ const prom = shim.applySegment(promisified, segment, true, this, arguments)
319
+ Contextualizer.link(null, prom, segment)
320
+ return prom
321
+ }
322
+ }
323
+ })
324
+ }
325
+ }
326
+ module.exports = PromiseShim
327
+
328
+ // -------------------------------------------------------------------------- //
329
+
330
+ /**
331
+ * @private
332
+ */
333
+ function _wrapExecutorContext(shim, args) {
334
+ const context = {
335
+ executor: args[0],
336
+ promise: null,
337
+ self: null,
338
+ args: null
339
+ }
340
+ contextExporter.__NR_executorContext = context
341
+ args[0] = contextExporter
342
+
343
+ function contextExporter(resolve, reject) {
344
+ context.self = this
345
+ context.args = shim.argsToArray.apply(shim, arguments)
346
+ context.args[0] = _wrapResolver(context, resolve)
347
+ context.args[1] = _wrapResolver(context, reject)
348
+ }
349
+ }
350
+
351
+ /**
352
+ * @private
353
+ */
354
+ function _wrapResolver(context, fn) {
355
+ return function wrappedResolveReject(val) {
356
+ const promise = context.promise
357
+ if (promise && promise.__NR_context) {
358
+ promise.__NR_context.getSegment().touch()
359
+ }
360
+ fn(val)
361
+ }
362
+ }
363
+
364
+ /**
365
+ * @private
366
+ */
367
+ function _wrapThen(shim, fn, name, useAllParams) {
368
+ // Don't wrap non-functions.
369
+ if (shim.isWrapped(fn) || !shim.isFunction(fn)) {
370
+ return
371
+ }
372
+
373
+ return function __NR_wrappedThen() {
374
+ if (!(this instanceof shim._class)) {
375
+ return fn.apply(this, arguments)
376
+ }
377
+
378
+ const thenSegment = shim.getSegment()
379
+ const promise = this
380
+
381
+ // Wrap up the arguments and execute the real then.
382
+ let isWrapped = false
383
+ const args = new Array(arguments.length)
384
+ for (let i = 0; i < arguments.length; ++i) {
385
+ args[i] = wrapHandler(arguments[i], i, arguments.length)
386
+ }
387
+ const next = fn.apply(this, args)
388
+
389
+ // If we got a promise (which we should have), link the parent's context.
390
+ if (!isWrapped && next instanceof shim._class && next !== promise) {
391
+ Contextualizer.link(promise, next, thenSegment)
392
+ }
393
+ return next
394
+
395
+ function wrapHandler(handler, i, length) {
396
+ if (
397
+ !shim.isFunction(handler) || // Not a function
398
+ shim.isWrapped(handler) || // Already wrapped
399
+ (!useAllParams && i !== (length - 1)) // Don't want all and not last
400
+ ) {
401
+ isWrapped = shim.isWrapped(handler)
402
+ return handler
403
+ }
404
+
405
+ return function __NR_wrappedThenHandler() {
406
+ if (!next || !next.__NR_context) {
407
+ return handler.apply(this, arguments)
408
+ }
409
+
410
+ let promSegment = next.__NR_context.getSegment()
411
+ const segment = promSegment || shim.getSegment()
412
+ if (segment && segment !== promSegment) {
413
+ next.__NR_context.setSegment(segment)
414
+ promSegment = segment
415
+ }
416
+
417
+ let ret = null
418
+ try {
419
+ ret = shim.applySegment(handler, promSegment, true, this, arguments)
420
+ } finally {
421
+ if (ret && typeof ret.then === 'function') {
422
+ ret = next.__NR_context.continueContext(ret)
423
+ }
424
+ }
425
+ return ret
426
+ }
427
+ }
428
+ }
429
+ }
430
+
431
+ /**
432
+ * @private
433
+ */
434
+ class Context {
435
+ constructor(segment) {
436
+ this.segments = [segment]
437
+ }
438
+
439
+ branch() {
440
+ return this.segments.push(null) - 1
441
+ }
442
+ }
443
+
444
+ /**
445
+ * @private
446
+ */
447
+ class Contextualizer {
448
+ constructor(idx, context) {
449
+ this.parentIdx = -1
450
+ this.idx = idx
451
+ this.context = context
452
+ this.child = null
453
+ }
454
+
455
+ static link(prev, next, segment) {
456
+ let ctxlzr = prev && prev.__NR_context
457
+ if (ctxlzr && !ctxlzr.isActive()) {
458
+ ctxlzr = prev.__NR_context = null
459
+ }
460
+
461
+ if (ctxlzr) {
462
+ // If prev has one child already, branch the context and update the child.
463
+ if (ctxlzr.child) {
464
+ // When the branch-point is the 2nd through nth link in the chain, it is
465
+ // necessary to track its segment separately so the branches can parent
466
+ // their segments on the branch-point.
467
+ if (ctxlzr.parentIdx !== -1) {
468
+ ctxlzr.idx = ctxlzr.context.branch()
469
+ }
470
+
471
+ // The first child needs to be updated to have its own branch as well. And
472
+ // each of that child's children must be updated with the new parent index.
473
+ // This is the only non-constant-time action for linking, but it only
474
+ // happens with branching promise chains specifically when the 2nd branch
475
+ // is added.
476
+ //
477
+ // Note: This does not account for branches of branches. That may result
478
+ // in improperly parented segments.
479
+ let parent = ctxlzr
480
+ let child = ctxlzr.child
481
+ const branchIdx = ctxlzr.context.branch()
482
+ do {
483
+ child.parentIdx = parent.idx
484
+ child.idx = branchIdx
485
+ parent = child
486
+ child = child.child
487
+ } while (child)
488
+
489
+ // We set the child to something falsey that isn't `null` so we can
490
+ // distinguish between having no child, having one child, and having
491
+ // multiple children.
492
+ ctxlzr.child = false
493
+ }
494
+
495
+ // If this is a branching link then create a new branch for the next promise.
496
+ // Otherwise, we can just piggy-back on the previous link's spot.
497
+ const idx = ctxlzr.child === false ? ctxlzr.context.branch() : ctxlzr.idx
498
+
499
+ // Create a new context for this next promise.
500
+ next.__NR_context = new Contextualizer(idx, ctxlzr.context)
501
+ next.__NR_context.parentIdx = ctxlzr.idx
502
+
503
+ // If this was our first child, remember it in case we have a 2nd.
504
+ if (ctxlzr.child === null) {
505
+ ctxlzr.child = next.__NR_context
506
+ }
507
+ } else if (segment) {
508
+ // This next promise is the root of a chain. Either there was no previous
509
+ // promise or the promise was created out of context.
510
+ next.__NR_context = new Contextualizer(0, new Context(segment))
511
+ }
512
+ }
513
+
514
+ isActive() {
515
+ const segments = this.context.segments
516
+ const segment = segments[this.idx] || segments[this.parentIdx] || segments[0]
517
+ return segment && segment.transaction.isActive()
518
+ }
519
+
520
+ getSegment() {
521
+ const segments = this.context.segments
522
+ let segment = segments[this.idx]
523
+ if (segment == null) {
524
+ segment = segments[this.idx] = segments[this.parentIdx] || segments[0]
525
+ }
526
+ return segment
527
+ }
528
+
529
+ setSegment(segment) {
530
+ return this.context.segments[this.idx] = segment
531
+ }
532
+
533
+ toJSON() {
534
+ // No-op.
535
+ }
536
+
537
+ continueContext(prom) {
538
+ const self = this
539
+ const nextContext = prom.__NR_context
540
+ if (!nextContext) {
541
+ return prom
542
+ }
543
+
544
+ // If we have `finally`, use that to sneak our context update.
545
+ if (typeof prom.finally === 'function') {
546
+ return prom.finally(__NR_continueContext)
547
+ }
548
+
549
+ // No `finally` means we need to hook into resolve and reject individually and
550
+ // pass through whatever happened.
551
+ return prom.then(function __NR_thenContext(val) {
552
+ __NR_continueContext()
553
+ return val
554
+ }, function __NR_catchContext(err) {
555
+ __NR_continueContext()
556
+ throw err // Re-throwing promise rejection, this is not New Relic's error.
557
+ })
558
+
559
+ function __NR_continueContext() {
560
+ self.setSegment(nextContext.getSegment())
561
+ }
562
+ }
563
+ }