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 +21 -0
- package/bin/compare-bench-results.js +159 -0
- package/bin/run-bench.js +87 -44
- package/lib/collector/remote-method.js +12 -0
- package/lib/config/index.js +3 -0
- package/lib/instrumentation/mongodb.js +158 -115
- package/package.json +2 -1
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return cb(err)
|
|
79
|
+
a.series([
|
|
80
|
+
function resolveGlobs(cb) {
|
|
81
|
+
if (!globs.length) {
|
|
82
|
+
return cb()
|
|
50
83
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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) {
|
package/lib/config/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
84
|
+
const GRID_OPS = [
|
|
83
85
|
'put',
|
|
84
86
|
'get',
|
|
85
87
|
'delete'
|
|
86
88
|
]
|
|
87
89
|
|
|
88
|
-
|
|
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
|
-
|
|
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
|
-
|
|
133
|
-
if (mongodb.instrument) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
153
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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.
|
|
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": {
|