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
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const semver = require('semver')
4
+
3
5
  // XXX: When this instrumentation is modularized, update this thread
4
6
  // with a cautionary note:
5
7
  // https://discuss.newrelic.com/t/feature-idea-using-mongoose-cursors-memory-leaking-very-quickly/49270/14
@@ -9,7 +11,7 @@
9
11
  // location.
10
12
 
11
13
  // legacy endpoint enumerations
12
- var DB_OPS = [
14
+ const DB_OPS = [
13
15
  'addUser',
14
16
  'authenticate',
15
17
  'collection',
@@ -37,7 +39,7 @@ var DB_OPS = [
37
39
  '_executeQueryCommand'
38
40
  ]
39
41
 
40
- var COLLECTION_OPS = [
42
+ const COLLECTION_OPS = [
41
43
  'aggregate',
42
44
  'bulkWrite',
43
45
  'count',
@@ -79,13 +81,13 @@ var COLLECTION_OPS = [
79
81
  'updateOne'
80
82
  ]
81
83
 
82
- var GRID_OPS = [
84
+ const GRID_OPS = [
83
85
  'put',
84
86
  'get',
85
87
  'delete'
86
88
  ]
87
89
 
88
- var CURSOR_OPS = [
90
+ const CURSOR_OPS = [
89
91
  'nextObject',
90
92
  'next',
91
93
  'toArray',
@@ -97,24 +99,10 @@ module.exports = initialize
97
99
 
98
100
  function initialize(agent, mongodb, moduleName, shim) {
99
101
  if (!mongodb) return
100
- var recordDesc = {
101
- 'Gridstore': {isQuery: false, makeDesc: function makeGridDesc(opName) {
102
- return {name:'GridFS-' + opName, callback: shim.LAST}
103
- }},
104
- 'OrderedBulkOperation': {isQuery: true, makeDesc: makeQueryDescFunc},
105
- 'UnorderedBulkOperation': {isQuery: true, makeDesc: makeQueryDescFunc},
106
- 'CommandCursor': {isQuery: true, makeDesc: makeQueryDescFunc},
107
- 'AggregationCursor': {isQuery: true, makeDesc: makeQueryDescFunc},
108
- 'Cursor': {isQuery: true, makeDesc: makeQueryDescFunc},
109
- 'Collection': {isQuery: true, makeDesc: makeQueryDescFunc},
110
- 'Db': {isQuery: false, makeDesc: function makeDbDesc() {
111
- return {callback: shim.LAST}
112
- }}
113
- }
114
102
 
115
103
  shim.setDatastore(shim.MONGODB)
116
104
  shim.setParser(function mongoQueryParser(operation) {
117
- var collection = this.collectionName || 'unknown'
105
+ let collection = this.collectionName || 'unknown'
118
106
  if (this.collection && this.collection.collectionName) {
119
107
  collection = this.collection.collectionName
120
108
  } else if (this.s && this.s.name) {
@@ -123,36 +111,51 @@ function initialize(agent, mongodb, moduleName, shim) {
123
111
  collection = this.ns.split(/\./)[1] || collection
124
112
  }
125
113
 
126
- return {
127
- operation: operation,
128
- collection: collection
129
- }
114
+ return {operation, collection}
130
115
  })
131
116
 
132
- // instrument using the apm api
133
- if (mongodb.instrument) {
134
- var instrumenter = mongodb.instrument(Object.create(null), instrumentModules)
135
- instrumenter.on('started', function onMongoEventStarted(evnt) {
136
- // This assumes that this `started` event is fired _after_ our wrapper
137
- // starts and creates the segment. We perform a check of the segment name
138
- // out of an excess of caution.
139
- var connId = evnt.connectionId
140
- if (connId) {
141
- // Mongo sticks the path to the domain socket in the "host" slot, but we
142
- // want it in the "port", so if we have a domain socket we need to change
143
- // the order of our parameters.
144
- if (connId.domainSocket) {
145
- shim.captureInstanceAttributes('localhost', connId.host, evnt.databaseName)
146
- } else {
147
- shim.captureInstanceAttributes(connId.host, connId.port, evnt.databaseName)
148
- }
149
- }
150
- })
117
+ const mongoVersion = shim.require('./package.json').version
118
+ if (semver.satisfies(mongoVersion, '>=3.0.6') && mongodb.instrument) {
119
+ instrument306(shim, mongodb)
120
+ } else if (mongodb.instrument) {
121
+ instrumentInstrument(shim, mongodb)
151
122
  } else {
152
- // fallback to legacy enumerations
153
- legacyInstrumentation()
123
+ instrumentLegacy(shim, mongodb)
124
+ }
125
+ }
126
+
127
+ function instrument306(shim, mongodb) {
128
+ const instrumenter = mongodb.instrument(Object.create(null), () => {})
129
+ captureAttributesOnStarted(shim, instrumenter)
130
+ instrumentLegacy(shim, mongodb)
131
+
132
+ if (shim.isFunction(instrumenter.uninstrument)) {
133
+ shim.agent.once('unload', function uninstrumentMongo() {
134
+ instrumenter.uninstrument()
135
+ })
136
+ }
137
+ }
138
+
139
+ function instrumentInstrument(shim, mongodb) {
140
+ const recordDesc = {
141
+ 'Gridstore': {isQuery: false, makeDesc: function makeGridDesc(opName) {
142
+ return {name:'GridFS-' + opName, callback: shim.LAST}
143
+ }},
144
+ 'OrderedBulkOperation': {isQuery: true, makeDesc: makeQueryDescFunc},
145
+ 'UnorderedBulkOperation': {isQuery: true, makeDesc: makeQueryDescFunc},
146
+ 'CommandCursor': {isQuery: true, makeDesc: makeQueryDescFunc},
147
+ 'AggregationCursor': {isQuery: true, makeDesc: makeQueryDescFunc},
148
+ 'Cursor': {isQuery: true, makeDesc: makeQueryDescFunc},
149
+ 'Collection': {isQuery: true, makeDesc: makeQueryDescFunc},
150
+ 'Db': {isQuery: false, makeDesc: function makeDbDesc() {
151
+ return {callback: shim.LAST}
152
+ }}
154
153
  }
155
154
 
155
+ // instrument using the apm api
156
+ const instrumenter = mongodb.instrument(Object.create(null), instrumentModules)
157
+ captureAttributesOnStarted(shim, instrumenter)
158
+
156
159
  function instrumentModules(err, instrumentations) {
157
160
  if (err) {
158
161
  shim.logger
@@ -182,9 +185,9 @@ function initialize(agent, mongodb, moduleName, shim) {
182
185
  var makeDescFunc = recordDesc[objectName].makeDesc
183
186
  var proto = object.prototype
184
187
  if (isQuery) {
185
- shim.recordQuery(proto, method, makeDescFunc(method))
188
+ shim.recordQuery(proto, method, makeDescFunc(shim, method))
186
189
  } else if (isQuery === false) { // could be unset
187
- shim.recordOperation(proto, method, makeDescFunc(method))
190
+ shim.recordOperation(proto, method, makeDescFunc(shim, method))
188
191
  } else {
189
192
  shim.logger.trace('No wrapping method found for %s', objectName)
190
193
  }
@@ -197,95 +200,135 @@ function initialize(agent, mongodb, moduleName, shim) {
197
200
  shim.recordOperation(object.prototype, 'pipe')
198
201
  }
199
202
  }
203
+ }
200
204
 
201
- function makeQueryDescFunc(methodName) {
202
- return function queryDescFunc() {
203
- var parameters = getInstanceAttributeParameters(this)
204
- var callback = shim.LAST
205
- if (methodName === 'each') {
206
- return {query: methodName, parameters: parameters, rowCallback: callback}
205
+ function captureAttributesOnStarted(shim, instrumenter) {
206
+ instrumenter.on('started', function onMongoEventStarted(evnt) {
207
+ // This assumes that this `started` event is fired _after_ our wrapper
208
+ // starts and creates the segment. We perform a check of the segment name
209
+ // out of an excess of caution.
210
+ const connId = evnt.connectionId
211
+ if (connId) {
212
+ // Mongo sticks the path to the domain socket in the "host" slot, but we
213
+ // want it in the "port", so if we have a domain socket we need to change
214
+ // the order of our parameters.
215
+ if (typeof connId === 'string') {
216
+ const parts = connId.split(':')
217
+ if (parts.length && parts[0][0] === '/') {
218
+ shim.captureInstanceAttributes('localhost', parts[0], evnt.databaseName)
219
+ } else {
220
+ shim.captureInstanceAttributes(parts[0], parts[1], evnt.databaseName)
221
+ }
222
+ } else if (connId.domainSocket) {
223
+ shim.captureInstanceAttributes('localhost', connId.host, evnt.databaseName)
224
+ } else {
225
+ shim.captureInstanceAttributes(connId.host, connId.port, evnt.databaseName)
207
226
  }
208
-
209
- // segment name does not actually use query string
210
- // method name is set as query so the query parser has access to the op name
211
- return {query: methodName, callback: callback, parameters: parameters}
212
227
  }
213
- }
228
+ })
229
+ }
214
230
 
215
- function getInstanceAttributeParameters(obj) {
216
- if (obj.db && obj.db.serverConfig) {
217
- shim.logger.trace('Adding datastore instance attributes from obj.db.serverConfig')
218
- var serverConfig = obj.db.serverConfig
219
- var db = serverConfig.db || serverConfig.dbInstance
220
- return doCapture(serverConfig, db && db.databaseName)
221
- } else if (obj.s && obj.s.db && obj.s.topology) {
222
- shim.logger.trace(
223
- 'Adding datastore instance attributes from obj.s.db + obj.s.topology'
231
+ function instrumentLegacy(shim, mongodb) {
232
+ instrumentCursor(mongodb.Cursor)
233
+ instrumentCursor(shim.require('./lib/aggregation_cursor'))
234
+ instrumentCursor(shim.require('./lib/command_cursor'))
235
+
236
+ if (mongodb.Collection && mongodb.Collection.prototype) {
237
+ const proto = mongodb.Collection.prototype
238
+ for (let i = 0; i < COLLECTION_OPS.length; i++) {
239
+ shim.recordQuery(
240
+ proto,
241
+ COLLECTION_OPS[i],
242
+ makeQueryDescFunc(shim, COLLECTION_OPS[i])
224
243
  )
225
- var databaseName = obj.s.db.databaseName || null
226
- var topology = obj.s.topology
227
- if (topology.s && topology.s.options) {
228
- return doCapture(topology.s.options, databaseName)
229
- }
230
244
  }
245
+ }
231
246
 
232
- shim.logger.trace('Could not find datastore instance attributes.')
233
- return {
234
- host: null,
235
- port_path_or_id: null,
236
- database_name: null
247
+ if (mongodb.Grid && mongodb.Grid.prototype) {
248
+ const proto = mongodb.Grid.prototype
249
+ for (let i = 0; i < CURSOR_OPS.length; i++) {
250
+ shim.recordOperation(proto, GRID_OPS[i],
251
+ {name:'GridFS-' + GRID_OPS[i], callback: shim.LAST})
237
252
  }
253
+ }
238
254
 
239
- function doCapture(conf, database) {
240
- var host = conf.host
241
- var port = conf.port
242
-
243
- // If using a domain socket, mongo stores the path as the host name, but we
244
- // pass it through the port value.
245
- if (
246
- (conf.socketOptions && conf.socketOptions.domainSocket) || /\.sock$/.test(host)
247
- ) {
248
- port = host
249
- host = 'localhost'
250
- }
255
+ if (mongodb.Db && mongodb.Db.prototype) {
256
+ const proto = mongodb.Db.prototype
257
+ shim.recordOperation(proto, DB_OPS, {callback: shim.LAST})
258
+ shim.recordOperation(mongodb.Db, 'connect', {callback: shim.LAST})
259
+ }
251
260
 
252
- return {
253
- host: host,
254
- port_path_or_id: port,
255
- database_name: database
261
+ function instrumentCursor(Cursor) {
262
+ if (Cursor && Cursor.prototype) {
263
+ const proto = Cursor.prototype
264
+ for (let i = 0; i < CURSOR_OPS.length; i++) {
265
+ shim.recordQuery(proto, CURSOR_OPS[i], makeQueryDescFunc(shim, CURSOR_OPS[i]))
256
266
  }
267
+
268
+ shim.recordQuery(proto, 'each', makeQueryDescFunc(shim, 'each'))
269
+ shim.recordOperation(proto, 'pipe')
257
270
  }
258
271
  }
272
+ }
259
273
 
260
- function legacyInstrumentation() {
261
- if (mongodb.Cursor && mongodb.Cursor.prototype) {
262
- var proto = mongodb.Cursor.prototype
263
- for (var i = 0; i < CURSOR_OPS.length; i++) {
264
- shim.recordQuery(proto, CURSOR_OPS[i], makeQueryDescFunc(CURSOR_OPS[i]))
265
- }
266
-
267
- shim.recordQuery(proto, 'each', makeQueryDescFunc('each'))
274
+ function makeQueryDescFunc(shim, methodName) {
275
+ if (methodName === 'each') {
276
+ return function eachDescFunc() {
277
+ const parameters = getInstanceAttributeParameters(shim, this)
278
+ return {query: methodName, parameters, rowCallback: shim.LAST}
268
279
  }
280
+ }
269
281
 
270
- if (mongodb.Collection && mongodb.Collection.prototype) {
271
- var proto = mongodb.Collection.prototype
272
- for (var i = 0; i < COLLECTION_OPS.length; i++) {
273
- shim.recordQuery(proto, COLLECTION_OPS[i], makeQueryDescFunc(COLLECTION_OPS[i]))
274
- }
282
+ return function queryDescFunc() {
283
+ // segment name does not actually use query string
284
+ // method name is set as query so the query parser has access to the op name
285
+ const parameters = getInstanceAttributeParameters(shim, this)
286
+ return {query: methodName, parameters, callback: shim.LAST}
287
+ }
288
+ }
289
+
290
+ function getInstanceAttributeParameters(shim, obj) {
291
+ if (obj.db && obj.db.serverConfig) {
292
+ shim.logger.trace('Adding datastore instance attributes from obj.db.serverConfig')
293
+ const serverConfig = obj.db.serverConfig
294
+ const db = serverConfig.db || serverConfig.dbInstance
295
+ return doCapture(serverConfig, db && db.databaseName)
296
+ } else if (obj.s && obj.s.db && obj.s.topology) {
297
+ shim.logger.trace(
298
+ 'Adding datastore instance attributes from obj.s.db + obj.s.topology'
299
+ )
300
+ const databaseName = obj.s.db.databaseName || null
301
+ const topology = obj.s.topology
302
+ if (topology.s && topology.s.options) {
303
+ return doCapture(topology.s.options, databaseName)
275
304
  }
305
+ }
276
306
 
277
- if (mongodb.Grid && mongodb.Grid.prototype) {
278
- var proto = mongodb.Grid.prototype
279
- for (var i = 0; i < CURSOR_OPS.length; i++) {
280
- shim.recordOperation(proto, GRID_OPS[i],
281
- {name:'GridFS-' + GRID_OPS[i], callback: shim.LAST})
282
- }
307
+ shim.logger.trace('Could not find datastore instance attributes.')
308
+ return {
309
+ host: null,
310
+ port_path_or_id: null,
311
+ database_name: null
312
+ }
313
+
314
+ function doCapture(conf, database) {
315
+ let host = conf.host
316
+ let port = conf.port
317
+
318
+ // If using a domain socket, mongo stores the path as the host name, but we
319
+ // pass it through the port value.
320
+ if (
321
+ (conf.socketOptions && conf.socketOptions.domainSocket) ||
322
+ /\.sock$/.test(host)
323
+ ) {
324
+ port = host
325
+ host = 'localhost'
283
326
  }
284
327
 
285
- if (mongodb.Db && mongodb.Db.prototype) {
286
- var proto = mongodb.Db.prototype
287
- shim.recordOperation(proto, DB_OPS, {callback: shim.LAST})
288
- shim.recordOperation(mongodb.Db, 'connect', {callback: shim.LAST})
328
+ return {
329
+ host: host,
330
+ port_path_or_id: port,
331
+ database_name: database
289
332
  }
290
333
  }
291
334
  }
@@ -6,8 +6,9 @@ var MODULE_TYPE = require('./shim/constants').MODULE_TYPE
6
6
  module.exports = function instrumentations() {
7
7
  return {
8
8
  'amqplib': {type: MODULE_TYPE.MESSAGE},
9
+ 'cassandra-driver': {type: MODULE_TYPE.DATASTORE},
9
10
  'connect': {type: MODULE_TYPE.WEB_FRAMEWORK},
10
- 'bluebird': {type: null},
11
+ 'bluebird': {type: MODULE_TYPE.PROMISE},
11
12
  'director': {type: MODULE_TYPE.WEB_FRAMEWORK},
12
13
  'express': {type: MODULE_TYPE.WEB_FRAMEWORK},
13
14
  'generic-pool': {type: MODULE_TYPE.GENERIC},
@@ -18,11 +19,11 @@ module.exports = function instrumentations() {
18
19
  'mongodb': {type: MODULE_TYPE.DATASTORE},
19
20
  'mysql': {type: MODULE_TYPE.DATASTORE},
20
21
  'node-cassandra-cql': {type: MODULE_TYPE.DATASTORE},
21
- 'cassandra-driver': {type: MODULE_TYPE.DATASTORE},
22
22
  'pg': {type: MODULE_TYPE.DATASTORE},
23
23
  'q': {type: null},
24
24
  'redis': {type: MODULE_TYPE.DATASTORE},
25
25
  'restify': {type: MODULE_TYPE.WEB_FRAMEWORK},
26
+ 'superagent': {module: '@newrelic/superagent'},
26
27
  'oracle': {type: null},
27
28
  'vision': {type: MODULE_TYPE.WEB_FRAMEWORK},
28
29
  'when': {type: null}
@@ -3,7 +3,7 @@
3
3
  var Heap = require('@tyriar/fibonacci-heap').FibonacciHeap
4
4
 
5
5
  function PriorityQueue(limit) {
6
- this.limit = limit || 10
6
+ this.limit = limit == null ? 10 : limit
7
7
  this.seen = 0
8
8
  this._data = new Heap()
9
9
 
@@ -24,8 +24,11 @@ PriorityQueue.prototype.getMinimumPriority = function getMinimumPriority() {
24
24
  }
25
25
 
26
26
  PriorityQueue.prototype.add = function add(value, priority) {
27
- priority = priority || Math.random()
28
27
  this.seen++
28
+ if (this.limit <= 0) {
29
+ return false
30
+ }
31
+ priority = priority || Math.random()
29
32
  if (this.length === this.limit) {
30
33
  return this._replace(value, priority)
31
34
  }
@@ -17,6 +17,12 @@ var MODULE_TYPE = {
17
17
  /** Messaging module, such as AMQP */
18
18
  MESSAGE: 'message',
19
19
 
20
+ /** Promise module, such as Bluebird */
21
+ PROMISE: 'promise',
22
+
23
+ /** @private */
24
+ TRANSACTION: 'transaction',
25
+
20
26
  /** Web server framework module, such as Express or Restify. */
21
27
  WEB_FRAMEWORK: 'web-framework'
22
28
  }
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  var hasOwnProperty = require('../util/properties').hasOwn
4
- var logger = require('../logger.js').child({component: 'DatastoreShim'})
4
+ var logger = require('../logger').child({component: 'DatastoreShim'})
5
5
  var metrics = require('../metrics/names')
6
6
  var parseSql = require('../db/query-parsers/sql')
7
7
  var ParsedStatement = require('../db/parsed-statement')
package/lib/shim/index.js CHANGED
@@ -1,14 +1,18 @@
1
1
  'use strict'
2
2
 
3
- var constants = require('./constants')
3
+ const constants = require('./constants')
4
4
 
5
- var Shim = require('./shim')
6
- var DatastoreShim = require('./datastore-shim')
7
- var MessageShim = require('./message-shim')
8
- var WebFrameworkShim = require('./webframework-shim')
5
+ const Shim = require('./shim')
6
+ const DatastoreShim = require('./datastore-shim')
7
+ const MessageShim = require('./message-shim')
8
+ const PromiseShim = require('./promise-shim')
9
+ const TransactionShim = require('./transaction-shim')
10
+ const WebFrameworkShim = require('./webframework-shim')
9
11
 
10
12
  exports.constants = constants
11
13
  exports.Shim = Shim
12
14
  exports.DatastoreShim = DatastoreShim
13
15
  exports.MessageShim = MessageShim
16
+ exports.PromiseShim = PromiseShim
17
+ exports.TransactionShim = TransactionShim
14
18
  exports.WebFrameworkShim = WebFrameworkShim
@@ -333,10 +333,6 @@ function recordProduce(nodule, properties, recordNamer) {
333
333
  }
334
334
 
335
335
  var name = _nameMessageSegment(shim, msgDesc, shim._metrics.PRODUCE)
336
- if (msgDesc.headers) {
337
- shim.insertCATRequestHeaders(msgDesc.headers, true)
338
- }
339
-
340
336
  if (!shim.agent.config.message_tracer.segment_parameters.enabled) {
341
337
  delete msgDesc.parameters
342
338
  } else if (msgDesc.routingKey) {
@@ -350,6 +346,11 @@ function recordProduce(nodule, properties, recordNamer) {
350
346
  promise: msgDesc.promise || false,
351
347
  callback: msgDesc.callback || null,
352
348
  recorder: genericRecorder,
349
+ inContext: function generateCATHeaders() {
350
+ if (msgDesc.headers) {
351
+ shim.insertCATRequestHeaders(msgDesc.headers, true)
352
+ }
353
+ },
353
354
  parameters: msgDesc.parameters || null
354
355
  }
355
356
  })