newrelic 4.7.0 → 4.8.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 CHANGED
@@ -1,3 +1,24 @@
1
+ ### 4.8.0 (2018-08-13):
2
+
3
+ * Added JSON-formatted output to benchmarks to enable automated benchmark comparison.
4
+
5
+ * Updated the benchmark runner to measure specifically userland CPU overhead.
6
+
7
+ * Added DatastoreShim benchmarks.
8
+
9
+ * Fixed MongoDB instrumentation for driver versions greater than 3.0.6.
10
+
11
+ Mongo 3.0.6 removed metadata the Agent relied upon to instrument the driver. This
12
+ fixes that by going back to the old method of manually listing all objects and
13
+ methods to instrument.
14
+
15
+ * Implemented enforcement of `max_payload_size_in_bytes` config value.
16
+
17
+ Any payload during the harvest sequence that exceeds the configured limit will
18
+ be discarded.
19
+
20
+ * Updated MySQL versioned tests to run against the latest release.
21
+
1
22
  ### 4.7.0 (2018-07-31):
2
23
 
3
24
  * Added support for distributed tracing.
@@ -0,0 +1,159 @@
1
+ 'use strict'
2
+ /* eslint-disable no-console */
3
+
4
+ const async = require('async')
5
+ const fs = require('fs')
6
+
7
+ if (process.argv.length !== 4) {
8
+ console.log('Usage: %s %s <baseline> <upstream>', process.argv[0], process.argv[1])
9
+ console.log(' <baseline> JSON file containing benchmark results for upstream')
10
+ console.log(' upstream comparison.')
11
+ console.log(' <downstream> JSON file containing benchmark results from')
12
+ console.log(' downstream branch to compare against base.')
13
+ process.exit(1)
14
+ }
15
+
16
+ async.map(process.argv.slice(2), (file, cb) => {
17
+ fs.readFile(file, {encoding: 'utf8'}, (err, data) => {
18
+ if (err) {
19
+ return cb(err)
20
+ }
21
+
22
+ let parsed = null
23
+ try {
24
+ parsed = JSON.parse(data)
25
+ } catch (parseError) {
26
+ return cb(parseError)
27
+ }
28
+
29
+ cb(null, parsed)
30
+ })
31
+ }, (err, resultFiles) => {
32
+ if (err) {
33
+ console.log('Failed to load files.')
34
+ console.log(err)
35
+ process.exit(2)
36
+ }
37
+
38
+ const baseline = resultFiles[0]
39
+ const downstream = resultFiles[1]
40
+
41
+ const baselineFiles = Object.keys(baseline)
42
+ const downstreamFiles = Object.keys(downstream)
43
+ const warnings = []
44
+
45
+ diffArrays(baselineFiles, downstreamFiles).forEach((file) => {
46
+ warnings.push(`- **WARNING**: File "${file}" in base but not branch.`)
47
+ })
48
+ diffArrays(downstreamFiles, baselineFiles).forEach((file) => {
49
+ warnings.push(`- **NOTE**: File "${file}" in branch but not base.`)
50
+ })
51
+
52
+ let allPassing = true
53
+ const details = baselineFiles.sort().map((testFile) => {
54
+ const base = baseline[testFile]
55
+ const down = downstream[testFile]
56
+
57
+ if (!down) {
58
+ return [
59
+ '<details>',
60
+ `<summary>${testFile} file missing from branch</summary>`,
61
+ '',
62
+ '</details>'
63
+ ].join('\n')
64
+ }
65
+
66
+ let filePassing = true
67
+ const baseTests = Object.keys(base)
68
+ const downTests = Object.keys(down)
69
+
70
+ diffArrays(baseTests, downTests).forEach((test) => {
71
+ warnings.push(`- **WARNING**: Test "${test}" in base but not branch.`)
72
+ })
73
+ diffArrays(downTests, baseTests).forEach((test) => {
74
+ warnings.push(`- **NOTE**: Test "${test}" in branch but not base.`)
75
+ })
76
+
77
+ const results = baseTests.sort().map((test) => {
78
+ const passes = compareResults(base[test], down[test])
79
+ filePassing = filePassing && passes
80
+
81
+ return [
82
+ '<details>',
83
+ `<summary>${test}: ${passMark(passes)}</summary>`,
84
+ '',
85
+ formatResults(base[test], down[test]),
86
+ '</details>',
87
+ ''
88
+ ].join('\n')
89
+ }).join('\n')
90
+ allPassing = allPassing && filePassing
91
+
92
+ return [
93
+ '<details>',
94
+ `<summary>${testFile}: ${passMark(filePassing)}</summary>`,
95
+ '',
96
+ results,
97
+ '</details>'
98
+ ].join('\n')
99
+ }).join('\n\n-----------------------------------------------------------\n\n')
100
+
101
+ if (warnings.length) {
102
+ console.log('### WARNINGS')
103
+ console.log(warnings.join('\n'))
104
+ console.log('')
105
+ }
106
+
107
+ console.log(`### Benchmark Results: ${passMark(allPassing)}`)
108
+ console.log('')
109
+ console.log('### Details')
110
+ console.log(details)
111
+
112
+ if (!allPassing) {
113
+ process.exitCode = -1
114
+ }
115
+ })
116
+
117
+ function diffArrays(a, b) {
118
+ return a.filter((elem) => !b.includes(elem))
119
+ }
120
+
121
+ function compareResults(base, down) {
122
+ const delta = base.mean - down.mean
123
+ const deltaPercent = delta / base.mean
124
+ return delta < 1 || deltaPercent < 2
125
+ }
126
+
127
+ function passMark(passes) {
128
+ return passes ? '✔' : '✘'
129
+ }
130
+
131
+ function formatResults(base, down) {
132
+ return [
133
+ `- ${formatField('numSamples', base, down)}`,
134
+ `- ${formatField('mean', base, down)}`,
135
+ `- ${formatField('stdDev', base, down)}`,
136
+ `- ${formatField('max', base, down)}`,
137
+ `- ${formatField('min', base, down)}`,
138
+ `- ${formatField('5thPercentile', base, down)}`,
139
+ `- ${formatField('95thPercentile', base, down)}`,
140
+ `- ${formatField('median', base, down)}`,
141
+ ].join('\n')
142
+ }
143
+
144
+ function formatField(field, base, down) {
145
+ const baseValue = base[field]
146
+ const downValue = down[field]
147
+ const diffValue = baseValue - downValue
148
+ const diffPercent = (100 * diffValue / baseValue).toFixed(2)
149
+ const prefix = diffValue >= 0 ? '+' : ''
150
+
151
+ return (
152
+ `${field}: ${fixValue(baseValue)} - ${fixValue(downValue)} =` +
153
+ ` ${fixValue(diffValue)} (${prefix}${diffPercent}%)`
154
+ )
155
+ }
156
+
157
+ function fixValue(value) {
158
+ return value % 1 ? value.toFixed(5) : value
159
+ }
package/bin/run-bench.js CHANGED
@@ -36,54 +36,97 @@ if (tests.length === 0 && globs.length === 0) {
36
36
  )
37
37
  }
38
38
 
39
- a.series([
40
- function resolveGlobs(cb) {
41
- if (!globs.length) {
42
- return cb()
43
- }
39
+ class ConsolePrinter {
40
+ /* eslint-disable no-console */
41
+ addTest(name, child) {
42
+ console.log(name)
43
+ child.stdout.on('data', (d) => process.stdout.write(d))
44
+ child.stderr.on('data', (d) => process.stderr.write(d))
45
+ child.once('exit', () => console.log(''))
46
+ }
47
+
48
+ finish() {
49
+ console.log('')
50
+ }
51
+ /* eslint-enable no-console */
52
+ }
53
+
54
+ class JSONPrinter {
55
+ constructor() {
56
+ this._tests = Object.create(null)
57
+ }
58
+
59
+ addTest(name, child) {
60
+ let output = ''
61
+ this._tests[name] = null
62
+ child.stdout.on('data', (d) => output += d.toString())
63
+ child.stdout.on('end', () => this._tests[name] = JSON.parse(output))
64
+ child.stderr.on('data', (d) => process.stderr.write(d))
65
+ }
66
+
67
+ finish() {
68
+ /* eslint-disable no-console */
69
+ console.log(JSON.stringify(this._tests, null, 2))
70
+ /* eslint-enable no-console */
71
+ }
72
+ }
73
+
74
+ run()
75
+
76
+ function run() {
77
+ const printer = opts.json ? new JSONPrinter() : new ConsolePrinter()
44
78
 
45
- a.map(globs, glob, function afterGlobbing(err, resolved) {
46
- if (err) {
47
- console.error('Failed to glob:', err)
48
- process.exitCode = -1
49
- return cb(err)
79
+ a.series([
80
+ function resolveGlobs(cb) {
81
+ if (!globs.length) {
82
+ return cb()
50
83
  }
51
- resolved.forEach(function mergeResolved(files) {
52
- files.forEach(function mergeFile(file) {
53
- if (tests.indexOf(file) === -1) {
54
- tests.push(file)
55
- }
84
+
85
+ a.map(globs, glob, function afterGlobbing(err, resolved) {
86
+ if (err) {
87
+ console.error('Failed to glob:', err)
88
+ process.exitCode = -1
89
+ return cb(err)
90
+ }
91
+ resolved.forEach(function mergeResolved(files) {
92
+ files.forEach(function mergeFile(file) {
93
+ if (tests.indexOf(file) === -1) {
94
+ tests.push(file)
95
+ }
96
+ })
56
97
  })
98
+ cb()
57
99
  })
58
- cb()
59
- })
60
- },
61
- function runBenchmarks(cb) {
62
- tests.sort()
63
- a.eachSeries(tests, function spawnEachFile(file, cb) {
64
- var test = path.relative(benchpath, file)
65
-
66
- console.log(test)
67
- var args = ['--expose-gc', file]
68
- if (opts.inspect) {
69
- args.unshift('--inspect-brk')
70
- }
71
- var child = cp.spawn('node', args, {cwd: cwd, stdio: 'inherit'})
72
- child.on('error', cb)
73
- child.on('exit', function onChildExit(code) {
74
- console.log('')
75
- if (code) {
76
- return cb(new Error('Benchmark exited with code ' + code))
100
+ },
101
+ function runBenchmarks(cb) {
102
+ tests.sort()
103
+ a.eachSeries(tests, function spawnEachFile(file, cb) {
104
+ var test = path.relative(benchpath, file)
105
+
106
+ var args = [file]
107
+ if (opts.inspect) {
108
+ args.unshift('--inspect-brk')
109
+ }
110
+ var child = cp.spawn('node', args, {cwd: cwd, stdio: 'pipe'})
111
+ printer.addTest(test, child)
112
+
113
+ child.on('error', cb)
114
+ child.on('exit', function onChildExit(code) {
115
+ if (code) {
116
+ return cb(new Error('Benchmark exited with code ' + code))
117
+ }
118
+ cb()
119
+ })
120
+ }, function afterSpawnEachFile(err) {
121
+ if (err) {
122
+ console.error('Spawning failed:', err)
123
+ process.exitCode = -2
124
+ return cb(err)
77
125
  }
78
126
  cb()
79
127
  })
80
- }, function afterSpawnEachFile(err) {
81
- if (err) {
82
- console.error('Spawning failed:', err)
83
- process.exitCode = -2
84
- return cb(err)
85
- }
86
- cb()
87
- })
88
- }
89
- ])
128
+ }
129
+ ], () => {
130
+ printer.finish()
131
+ })
132
+ }
@@ -10,6 +10,7 @@ var stringify = require('json-stringify-safe')
10
10
  var Sink = require('../util/stream-sink')
11
11
  var agents = require('./http-agents')
12
12
  var certificates = require('./ssl/certificates')
13
+ const isValidLength = require('../util/byte-limit').isValidLength
13
14
 
14
15
  /*
15
16
  *
@@ -147,8 +148,19 @@ RemoteMethod.prototype._safeRequest = function _safeRequest(options) {
147
148
  var protocol = 'https'
148
149
  var logConfig = this._config.logging
149
150
  var auditLog = this._config.audit_log
151
+ const maxPayloadSize = this._config.max_payload_size_in_bytes
150
152
  var level = 'trace'
151
153
 
154
+ if (!isValidLength(options.body, maxPayloadSize + 1)) {
155
+ logger.warn(
156
+ 'The payload size %d being sent to method %s exceeded the maximum size of %d',
157
+ Buffer.byteLength(options.body, 'utf8'),
158
+ this.name,
159
+ maxPayloadSize
160
+ )
161
+ throw new Error('Maximum payload size exceeded')
162
+ }
163
+
152
164
  // If trace level is not explicity enabled check to see if the audit log is
153
165
  // enabled.
154
166
  if (logConfig != null && logConfig.level !== 'trace' && auditLog.enabled) {
@@ -21,6 +21,7 @@ var logger = null // Lazy-loaded in `initialize`.
21
21
  /**
22
22
  * CONSTANTS -- we gotta lotta 'em
23
23
  */
24
+ const DEFAULT_MAX_PAYLOAD_SIZE_IN_BYTES = 1000000
24
25
  const DEFAULT_CONFIG_PATH = require.resolve('./default')
25
26
  const DEFAULT_CONFIG = require('./default').config
26
27
  const DEFAULT_FILENAME = 'newrelic.js'
@@ -126,6 +127,7 @@ function Config(config) {
126
127
  this.trusted_account_key = null
127
128
  this.sampling_target = 10
128
129
  this.sampling_target_period_in_seconds = 60
130
+ this.max_payload_size_in_bytes = DEFAULT_MAX_PAYLOAD_SIZE_IN_BYTES
129
131
 
130
132
  // how frequently harvester runs
131
133
  this.data_report_period = 60
@@ -273,6 +275,7 @@ Config.prototype._fromServer = function _fromServer(params, key) {
273
275
  case 'collect_errors':
274
276
  case 'collect_traces':
275
277
  case 'product_level':
278
+ case 'max_payload_size_in_bytes':
276
279
  case 'sampling_target':
277
280
  case 'sampling_target_period_in_seconds':
278
281
  case 'trusted_account_ids':
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "4.7.0",
3
+ "version": "4.8.0",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "licenses": [
6
6
  {
@@ -105,6 +105,7 @@
105
105
  "scripts": {
106
106
  "bench": "node ./bin/run-bench.js",
107
107
  "versioned-tests": "./bin/run-versioned-tests.sh",
108
+ "versioned": "./bin/run-versioned-tests.sh",
108
109
  "test": "make test"
109
110
  },
110
111
  "bin": {