Skip to content

Commit 868e5bd

Browse files
feat: Updated RemoteMethod to handle sending pprof_data through the collector (newrelic#3734)
1 parent 738045c commit 868e5bd

File tree

6 files changed

+85
-55
lines changed

6 files changed

+85
-55
lines changed

lib/collector/api.js

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -90,24 +90,31 @@ function CollectorAPI(agent) {
9090
* save some GC time?
9191
*/
9292
this._methods = {}
93-
for (const name of [
94-
'preconnect',
95-
'connect',
96-
'agent_settings',
97-
'error_data',
98-
'metric_data',
99-
'transaction_sample_data',
100-
'shutdown',
101-
'analytic_event_data',
102-
'custom_event_data',
103-
'sql_trace_data',
104-
'error_event_data',
105-
'span_event_data',
106-
'log_event_data',
107-
'pprof_data'
93+
for (const methodOptions of [
94+
{ name: 'preconnect' },
95+
{ name: 'connect' },
96+
{ name: 'agent_settings' },
97+
{ name: 'error_data' },
98+
{ name: 'metric_data' },
99+
{ name: 'transaction_sample_data' },
100+
{ name: 'shutdown' },
101+
{ name: 'analytic_event_data' },
102+
{ name: 'custom_event_data' },
103+
{ name: 'sql_trace_data' },
104+
{ name: 'error_event_data' },
105+
{ name: 'span_event_data' },
106+
{ name: 'log_event_data' },
107+
{ name: 'pprof_data', contentType: 'application/octet-stream' },
108108
]) {
109-
const method = new RemoteMethod(name, agent, initialEndpoint)
110-
this._methods[name] = method
109+
const method = new RemoteMethod(
110+
{
111+
name: methodOptions.name,
112+
agent,
113+
endpoint: initialEndpoint,
114+
contentType: methodOptions.contentType
115+
}
116+
)
117+
this._methods[methodOptions.name] = method
111118
}
112119
}
113120

lib/collector/remote-method.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const USER_AGENT_FORMAT = 'NewRelic-NodeAgent/%s (nodejs %s %s-%s)'
3434
const ENCODING_HEADER = 'CONTENT-ENCODING'
3535
const DEFAULT_ENCODING = 'identity'
3636

37-
function RemoteMethod(name, agent, endpoint) {
37+
function RemoteMethod({ name, agent, endpoint, contentType = 'application/json' }) {
3838
if (!name) {
3939
throw new TypeError('Must include name of method to invoke on collector.')
4040
}
@@ -48,6 +48,7 @@ function RemoteMethod(name, agent, endpoint) {
4848
this._protocolVersion = 17
4949

5050
this.endpoint = endpoint
51+
this._contentType = contentType
5152
}
5253

5354
RemoteMethod.prototype.updateEndpoint = function updateEndpoint(endpoint) {
@@ -377,7 +378,7 @@ RemoteMethod.prototype._headers = function _headers(options) {
377378
'User-Agent': agent,
378379
Connection: 'Keep-Alive',
379380
'Content-Length': byteLength(options.body),
380-
'Content-Type': 'application/json'
381+
'Content-Type': this._contentType
381382
}
382383

383384
if (options.compressed) {

test/integration/collector-remote-method.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ test('DataSender (callback style) talking to fake collector', async (t) => {
6060
res.json({ payload: result })
6161
})
6262

63-
const method = new RemoteMethod('preconnect', agent, endpoint)
63+
const method = new RemoteMethod({ name: 'preconnect', agent, endpoint })
6464
method._post('[]', {}, (error, results) => {
6565
if (error) {
6666
return reject(error)
@@ -105,7 +105,7 @@ test('should record metrics about data usage', async (t) => {
105105
host: collector.host,
106106
port: collector.port
107107
}
108-
const method = new RemoteMethod('preconnect', agent, endpoint)
108+
const method = new RemoteMethod({ name: 'preconnect', agent, endpoint })
109109

110110
const byteLength = (data) => Buffer.byteLength(JSON.stringify(data), 'utf8')
111111
const payload = [{ hello: 'world' }]

test/integration/keep-alive.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,5 +79,5 @@ function createRemoteMethod(port, cert) {
7979
config.certificates = [cert.certificate]
8080

8181
const agent = { config, metrics: { measureBytes() {} } }
82-
return new RemoteMethod('fake', agent, endpoint)
82+
return new RemoteMethod({ name: 'fake', agent, endpoint })
8383
}

test/unit/collector/api.test.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,10 @@ const apiMethods = [
314314
}
315315
}
316316
]
317+
},
318+
{
319+
key: 'pprof_data',
320+
data: Buffer.from('fake pprof data')
317321
}
318322
]
319323

test/unit/collector/remote-method.test.js

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,32 +22,42 @@ const RUN_ID = 1337
2222
const BARE_AGENT = { config: {}, metrics: { measureBytes() {} } }
2323

2424
test('should require a name for the method to call', () => {
25-
assert.throws(() => new RemoteMethod())
25+
assert.throws(() => new RemoteMethod({}))
2626
})
2727

2828
test('should require an agent for the method to call', () => {
29-
assert.throws(() => new RemoteMethod('test'))
29+
assert.throws(() => new RemoteMethod({ name: 'test' }))
3030
})
3131

3232
test('should expose a call method as its public API', () => {
33-
const method = new RemoteMethod('test', BARE_AGENT)
33+
const method = new RemoteMethod({ name: 'test', agent: BARE_AGENT })
3434
assert.equal(typeof method.invoke, 'function')
3535
})
3636

3737
test('should expose its name', () => {
38-
const method = new RemoteMethod('test', BARE_AGENT)
38+
const method = new RemoteMethod({ name: 'test', agent: BARE_AGENT })
3939
assert.equal(method.name, 'test')
4040
})
4141

4242
test('should default to protocol 17', () => {
43-
const method = new RemoteMethod('test', BARE_AGENT)
43+
const method = new RemoteMethod({ name: 'test', agent: BARE_AGENT })
4444
assert.equal(method._protocolVersion, 17)
4545
})
4646

47+
test('should default to json content type', () => {
48+
const method = new RemoteMethod({ name: 'test', agent: BARE_AGENT })
49+
assert.equal(method._contentType, 'application/json')
50+
})
51+
52+
test('should use octet stream for content type for pprof_data method', () => {
53+
const method = new RemoteMethod({ name: 'pprof_data', agent: BARE_AGENT, contentType: 'application/octet-stream' })
54+
assert.equal(method._contentType, 'application/octet-stream')
55+
})
56+
4757
test('serialize', async (t) => {
4858
t.beforeEach((ctx) => {
4959
ctx.nr = {}
50-
ctx.nr.method = new RemoteMethod('test', BARE_AGENT)
60+
ctx.nr.method = new RemoteMethod({ name: 'test', agent: BARE_AGENT })
5161
})
5262

5363
await t.test('should JSON-encode the given payload', (t, end) => {
@@ -102,7 +112,7 @@ test('_safeRequest', async (t) => {
102112
ctx.nr.agent = helper.instrumentMockedAgent()
103113
ctx.nr.agent.config = { max_payload_size_in_bytes: 100 }
104114

105-
ctx.nr.method = new RemoteMethod('test', ctx.nr.agent)
115+
ctx.nr.method = new RemoteMethod({ name: 'test', agent: ctx.nr.agent, endpoint: {} })
106116

107117
ctx.nr.options = {
108118
host: 'collector.newrelic.com',
@@ -181,7 +191,7 @@ test('_safeRequest', async (t) => {
181191

182192
test('when calling a method on the collector', async (t) => {
183193
await t.test('should not throw when dealing with compressed data', (t, end) => {
184-
const method = new RemoteMethod('test', BARE_AGENT, { host: 'localhost' })
194+
const method = new RemoteMethod({ name: 'test', agent: BARE_AGENT, endpoint: { host: 'localhost' } })
185195
method._shouldCompress = () => true
186196
method._safeRequest = (options) => {
187197
assert.equal(options.body.readUInt8(0), 31)
@@ -192,7 +202,7 @@ test('when calling a method on the collector', async (t) => {
192202
})
193203

194204
await t.test('should not throw when preparing uncompressed data', (t, end) => {
195-
const method = new RemoteMethod('test', BARE_AGENT, { host: 'localhost' })
205+
const method = new RemoteMethod({ name: 'test', agent: BARE_AGENT, endpoint: { host: 'localhost' } })
196206
method._safeRequest = (options) => {
197207
assert.equal(options.body, '"data"')
198208
end()
@@ -219,7 +229,7 @@ test('when the connection fails', async (t) => {
219229

220230
const config = { max_payload_size_in_bytes: 100_000 }
221231
const endpoint = { host: 'localhost', port: 8765 }
222-
const method = new RemoteMethod('TEST', { ...BARE_AGENT, config }, endpoint)
232+
const method = new RemoteMethod({ name: 'TEST', agent: { ...BARE_AGENT, config }, endpoint })
223233
method.invoke({ message: 'none' }, {}, (error) => {
224234
assert.equal(error.code, 'ECONNREFUSED')
225235
end()
@@ -239,7 +249,7 @@ test('when the connection fails', async (t) => {
239249

240250
const config = { max_payload_size_in_bytes: 100_000 }
241251
const endpoint = { host: 'failed.domain.cxlrg', port: 80 }
242-
const method = new RemoteMethod('TEST', { ...BARE_AGENT, config }, endpoint)
252+
const method = new RemoteMethod({ name: 'TEST', agent: { ...BARE_AGENT, config }, endpoint })
243253
method.invoke([], {}, (error) => {
244254
assert.equal(error.message, 'no dns')
245255
end()
@@ -264,9 +274,11 @@ test('when posting to collector', async (t) => {
264274
ctx.nr.endpoint = { host: collector.host, port: collector.port }
265275

266276
ctx.nr.method = new RemoteMethod(
267-
'metric_data',
268-
{ ...BARE_AGENT, config: ctx.nr.config },
269-
ctx.nr.endpoint
277+
{
278+
name: 'metric_data',
279+
agent: { ...BARE_AGENT, config: ctx.nr.config },
280+
endpoint: ctx.nr.endpoint
281+
}
270282
)
271283
})
272284

@@ -389,9 +401,11 @@ test('when generating headers for a plain request', async (t) => {
389401

390402
const body = 'test☃'
391403
ctx.nr.method = new RemoteMethod(
392-
body,
393-
{ ...BARE_AGENT, config: ctx.nr.config },
394-
ctx.nr.endpoint
404+
{
405+
name: body,
406+
agent: { ...BARE_AGENT, config: ctx.nr.config },
407+
endpoint: ctx.nr.endpoint
408+
}
395409
)
396410

397411
ctx.nr.options = { body, compressed: false }
@@ -453,9 +467,11 @@ test('when generating headers for a compressed request', async (t) => {
453467

454468
const body = 'test☃'
455469
ctx.nr.method = new RemoteMethod(
456-
body,
457-
{ ...BARE_AGENT, config: ctx.nr.config },
458-
ctx.nr.endpoint
470+
{
471+
name: body,
472+
agent: { ...BARE_AGENT, config: ctx.nr.config },
473+
endpoint: ctx.nr.endpoint
474+
}
459475
)
460476

461477
ctx.nr.options = { body, compressed: true }
@@ -509,9 +525,11 @@ test('when generating headers request URL', async (t) => {
509525
ctx.nr.endpoint = { host: collector.host, port: collector.port }
510526

511527
ctx.nr.method = new RemoteMethod(
512-
TEST_METHOD,
513-
{ ...BARE_AGENT, config: ctx.nr.config },
514-
ctx.nr.endpoint
528+
{
529+
name: TEST_METHOD,
530+
agent: { ...BARE_AGENT, config: ctx.nr.config },
531+
endpoint: ctx.nr.endpoint
532+
}
515533
)
516534

517535
ctx.nr.parsed = new URL(`http://${collector.host}:${collector.port}${ctx.nr.method._path()}`)
@@ -540,15 +558,15 @@ test('when generating headers request URL', async (t) => {
540558

541559
await t.test('should not include the agent run ID when not set', (t) => {
542560
const { config, endpoint } = t.nr
543-
const method = new RemoteMethod(TEST_METHOD, { config }, endpoint)
561+
const method = new RemoteMethod({ name: TEST_METHOD, agent: { config }, endpoint })
544562
const parsed = new URL(`http://${endpoint.host}:${endpoint.port}${method._path()}`)
545563
assert.equal(parsed.searchParams.has('run_id'), false)
546564
})
547565

548566
await t.test('should include the agent run ID when set', (t) => {
549567
const { config, endpoint } = t.nr
550568
config.run_id = TEST_RUN_ID
551-
const method = new RemoteMethod(TEST_METHOD, { config }, endpoint)
569+
const method = new RemoteMethod({ name: TEST_METHOD, agent: { config }, endpoint })
552570
const parsed = new URL(`http://${endpoint.host}:${endpoint.port}${method._path()}`)
553571
assert.equal(parsed.searchParams.get('run_id'), TEST_RUN_ID)
554572
})
@@ -569,7 +587,7 @@ test('when generating the User-Agent string', async (t) => {
569587
pkg.version = TEST_VERSION
570588

571589
ctx.nr.config = new Config({})
572-
ctx.nr.method = new RemoteMethod('test', { config: ctx.nr.config }, {})
590+
ctx.nr.method = new RemoteMethod({ name: 'test', agent: { config: ctx.nr.config }, endpoint: {} })
573591
ctx.nr.userAgent = ctx.nr.method._userAgent()
574592
})
575593

@@ -618,8 +636,8 @@ test('record data usage supportability metrics', async (t) => {
618636
await t.test('should aggregate bytes of uploaded payloads', async (t) => {
619637
const { agent, endpoint } = t.nr
620638

621-
const method1 = new RemoteMethod('preconnect', agent, endpoint)
622-
const method2 = new RemoteMethod('connect', agent, endpoint)
639+
const method1 = new RemoteMethod({ name: 'preconnect', agent, endpoint })
640+
const method2 = new RemoteMethod({ name: 'connect', agent, endpoint })
623641
const payload = [{ hello: 'world' }]
624642
const expectedSize = 19
625643
const totalMetric = [2, expectedSize * 2, 79, expectedSize, expectedSize, 722]
@@ -653,7 +671,7 @@ test('record data usage supportability metrics', async (t) => {
653671
const payloadSize = byteLength(payload)
654672
const responseSize = byteLength(response)
655673
const metric = [1, payloadSize, responseSize, 19, 19, 361]
656-
const method = new RemoteMethod('preconnect', agent, endpoint)
674+
const method = new RemoteMethod({ name: 'preconnect', agent, endpoint })
657675

658676
// Stub call to NR so we can test response payload metrics:
659677
method._post = (data, nrHeaders, callback) => {
@@ -679,7 +697,7 @@ test('record data usage supportability metrics', async (t) => {
679697
const payload = [{ hello: 'world' }]
680698
const payloadSize = byteLength(payload)
681699
const metric = [1, payloadSize, 0, 19, 19, 361]
682-
const method = new RemoteMethod('preconnect', agent, endpoint)
700+
const method = new RemoteMethod({ name: 'preconnect', agent, endpoint })
683701

684702
// Stub call to NR so we can test response payload metrics:
685703
method._post = (data, nrHeaders, callback) => {
@@ -741,7 +759,7 @@ test('_safeRequest logging', async (t) => {
741759

742760
await t.test('should redact license key in logs', (t) => {
743761
const { RemoteMethod, options, config } = t.nr
744-
const method = new RemoteMethod('test', { config })
762+
const method = new RemoteMethod({ name: 'test', agent: { config }, endpoint: {} })
745763
method._safeRequest(options)
746764
assert.deepStrictEqual(
747765
t.nr.logs.trace,
@@ -765,7 +783,7 @@ test('_safeRequest logging', async (t) => {
765783
config.logging = { level: 'info' }
766784
config.audit_log = { enabled: true, endpoints: ['test'] }
767785

768-
const method = new RemoteMethod('test', { config })
786+
const method = new RemoteMethod({ name: 'test', agent: { config }, endpoint: {} })
769787
method._safeRequest(options)
770788
assert.deepStrictEqual(
771789
t.nr.logs.info,
@@ -787,7 +805,7 @@ test('_safeRequest logging', async (t) => {
787805
const { RemoteMethod, options, config, logger } = t.nr
788806
logger.traceEnabled = () => false
789807

790-
const method = new RemoteMethod('test', { config })
808+
const method = new RemoteMethod({ name: 'test', agent: { config }, endpoint: {} })
791809
method._safeRequest(options)
792810
assert.equal(t.nr.logs.info.length, 0)
793811
assert.equal(t.nr.logs.trace.length, 0)

0 commit comments

Comments
 (0)