Skip to content

Commit 607f128

Browse files
authored
feat(profiling): adding heapdump (#37)
* feat(profiling): adding heapdump * feat(control): adding remote control
1 parent 974f64b commit 607f128

File tree

18 files changed

+279
-5
lines changed

18 files changed

+279
-5
lines changed

e2e/reportApmMetrics.spec.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ test('should report apm metrics', function (t) {
3333
TRACE_API_KEY_TEST,
3434
42,
3535
Number.MAX_SAFE_INTEGER)
36+
serviceMocks.mockControlRequest(
37+
TRACE_COLLECTOR_API_URL,
38+
TRACE_API_KEY_TEST,
39+
42,
40+
Number.MAX_SAFE_INTEGER)
3641
serviceMocks.mockApmMetricsRequest(
3742
TRACE_COLLECTOR_API_URL,
3843
TRACE_API_KEY_TEST,

e2e/reportHttpRequest.spec.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ test('should report http requests', function (t) {
3838
TRACE_API_KEY_TEST,
3939
42,
4040
Number.MAX_SAFE_INTEGER)
41+
serviceMocks.mockControlRequest(
42+
TRACE_COLLECTOR_API_URL,
43+
TRACE_API_KEY_TEST,
44+
42,
45+
Number.MAX_SAFE_INTEGER)
4146
serviceMocks.mockHttpTransactionRequest(
4247
TRACE_COLLECTOR_API_URL,
4348
TRACE_API_KEY_TEST,

e2e/utils/serviceMocks.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,19 @@ function mockIncomingEdgeMetricsRequest (url, apiKey, serviceKey, maxTimes, call
6464
.reply(callback || 200)
6565
}
6666

67+
function mockControlRequest (url, apiKey, serviceKey, maxTimes, callback) {
68+
return nock(url, {
69+
reqheaders: {
70+
'Authorization': 'Bearer ' + apiKey
71+
}
72+
})
73+
.post('/service/42/control')
74+
.times(maxTimes)
75+
.reply(callback || 200, {
76+
commands: []
77+
})
78+
}
79+
6780
function mockHttpTransactionRequest (url, apiKey, callback) {
6881
return nock(url, {
6982
reqheaders: {
@@ -80,5 +93,6 @@ module.exports = {
8093
mockRpmMetricsRequest: mockRpmMetricsRequest,
8194
mockExternalEdgeMetricsRequest: mockExternalEdgeMetricsRequest,
8295
mockIncomingEdgeMetricsRequest: mockIncomingEdgeMetricsRequest,
83-
mockHttpTransactionRequest: mockHttpTransactionRequest
96+
mockHttpTransactionRequest: mockHttpTransactionRequest,
97+
mockControlRequest: mockControlRequest
8498
}

lib/agent/api/index.js

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ function CollectorApi (options) {
1717
this.COLLECTOR_API_INCOMING_EDGE_METRICS = url.resolve(options.collectorApiUrl, options.collectorApiIncomingEdgeMetricsEndpoint)
1818
this.COLLECTOR_API_EXTERNAL_EDGE_METRICS = url.resolve(options.collectorApiUrl, options.collectorApiExternalEdgeMetricsEndpoint)
1919
this.COLLECTOR_API_HEALTHCHECK = url.resolve(options.collectorApiUrl, options.collectorApiHealthcheckEndpoint)
20+
this.COLLECTOR_API_PROFILER_MEMORY_HEAPDUMP = url.resolve(options.collectorApiUrl, options.collectorApiProfilerMemoryHeapdumpEndpoint)
21+
this.COLLECTOR_API_CONTROL = url.resolve(options.collectorApiUrl, options.collectorApiControlEndpoint)
2022

2123
this.collectorLanguage = options.collectorLanguage
2224
this.apiKey = options.apiKey
@@ -48,10 +50,12 @@ CollectorApi.prototype._withInstanceInfo = function (data) {
4850
}, data)
4951
}
5052

51-
CollectorApi.prototype._send = function (destinationUrl, data) {
53+
CollectorApi.prototype._send = function (destinationUrl, data, callback) {
5254
var opts = url.parse(destinationUrl)
5355
var payload = JSON.stringify(data)
5456

57+
callback = callback || function () {}
58+
5559
var req = https.request({
5660
hostname: opts.hostname,
5761
port: opts.port,
@@ -66,12 +70,13 @@ CollectorApi.prototype._send = function (destinationUrl, data) {
6670
}
6771
}, function (res) {
6872
res.setEncoding('utf8')
69-
res.pipe(bl(function (err) {
73+
res.pipe(bl(function (err, result) {
7074
if (err) {
7175
debug('There was an error when connecting to the Trace API', err)
7276
return
7377
}
7478

79+
callback(null, result)
7580
debug('HTTP Traces sent successfully')
7681
}))
7782
})
@@ -81,6 +86,7 @@ CollectorApi.prototype._send = function (destinationUrl, data) {
8186
req.on('error', function (error) {
8287
console.error('error: [trace]', 'There was an error connecting to the Trace servers. Make sure your servers can reach', opts.hostname)
8388
debug('error connecting to the Trace servers', error)
89+
callback(error)
8490
})
8591
req.write(payload)
8692
req.end()
@@ -116,6 +122,16 @@ CollectorApi.prototype.sendApmMetrics = function (data) {
116122
this._send(url, this._withInstanceInfo(data))
117123
}
118124

125+
CollectorApi.prototype.sendMemorySnapshot = function (data) {
126+
if (!isNumber(this.serviceKey)) {
127+
debug('Service id not present, cannot send heapdump')
128+
return
129+
}
130+
131+
var url = util.format(this.COLLECTOR_API_PROFILER_MEMORY_HEAPDUMP, this.serviceKey)
132+
this._send(url, this._withInstanceInfo(data))
133+
}
134+
119135
CollectorApi.prototype.sendExternalEdgeMetrics = function (data) {
120136
if (!isNumber(this.serviceKey)) {
121137
debug('Service id not present, cannot send metrics')
@@ -139,6 +155,29 @@ CollectorApi.prototype.sendIncomingEdgeMetrics = function (data) {
139155
}))
140156
}
141157

158+
CollectorApi.prototype.getUpdates = function (data, callback) {
159+
if (!isNumber(this.serviceKey)) {
160+
debug('Service id not present, cannot get updates')
161+
return
162+
}
163+
164+
var url = util.format(this.COLLECTOR_API_CONTROL, this.serviceKey)
165+
166+
this._send(url, this._withInstanceInfo({
167+
latestCommandId: data.latestCommandId
168+
}), function (err, response) {
169+
if (err) {
170+
return callback(err)
171+
}
172+
173+
try {
174+
callback(null, JSON.parse(response.toString('utf8')))
175+
} catch (ex) {
176+
return callback(ex)
177+
}
178+
})
179+
}
180+
142181
CollectorApi.prototype.sendSamples = function (samples, sync) {
143182
var url = this.COLLECTOR_API_SAMPLE
144183
var metadata = {

lib/agent/api/index.spec.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ describe('The Trace CollectorApi module', function () {
2222
collectorApiIncomingEdgeMetricsEndpoint: '/service/%s/edge-incoming',
2323
collectorApiExternalEdgeMetricsEndpoint: '/service/%s/edge-external',
2424
collectorApiHealthcheckEndpoint: '/service/%s/healthcheck',
25+
collectorApiProfilerMemoryHeapdumpEndpoint: '/service/%s/memory-heapdump',
26+
collectorApiControlEndpoint: '/service/%s/control',
2527
system: {
2628
hostname: 'test.org',
2729
processVersion: '4.3.1',

lib/agent/control/control.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
var debug = require('debug')('risingstack/trace')
2+
3+
function Control (options) {
4+
var _this = this
5+
this.collectorApi = options.collectorApi
6+
this.controlBus = options.controlBus
7+
this.config = options.config
8+
this.updateInterval = this.config.updateInterval
9+
this.latestCommandId = undefined
10+
11+
this.getUpdates()
12+
13+
this.interval = setInterval(function () {
14+
_this.getUpdates()
15+
}, this.updateInterval)
16+
}
17+
18+
Control.prototype.getUpdates = function () {
19+
var _this = this
20+
this.collectorApi.getUpdates({
21+
latestCommandId: _this.latestCommandId
22+
}, function (err, result) {
23+
if (err) {
24+
return debug(err)
25+
}
26+
27+
_this.latestCommandId = result.latestCommandId
28+
29+
result.commands = result.commands || []
30+
result.commands.forEach(function (command) {
31+
_this.controlBus.emit(command.command, command.data)
32+
})
33+
})
34+
}
35+
36+
function create (options) {
37+
return new Control(options)
38+
}
39+
40+
module.exports.create = create

lib/agent/control/control.spec.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
var expect = require('chai').expect
2+
3+
var Control = require('./')
4+
5+
describe('The Control module', function () {
6+
it('interprets commands', function () {
7+
var collectorApi = {
8+
getUpdates: function (options, cb) {
9+
cb(null, {
10+
commands: [{
11+
id: 123,
12+
command: 'memory-heapdump'
13+
}]
14+
})
15+
}
16+
}
17+
18+
var controlBus = {
19+
on: function () {
20+
21+
},
22+
emit: this.sandbox.spy()
23+
}
24+
25+
var profiler = Control.create({
26+
collectorApi: collectorApi,
27+
controlBus: controlBus,
28+
config: {
29+
updateInterval: 1
30+
}
31+
})
32+
profiler.getUpdates()
33+
expect(controlBus.emit).to.be.calledWith('memory-heapdump', undefined)
34+
})
35+
})

lib/agent/control/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('./control')

lib/agent/index.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
var EventEmitter = require('events').EventEmitter
2+
13
var debug = require('debug')('risingstack/trace')
2-
var microtime = require('../optionalDependencies/microtime')
34
var uuid = require('node-uuid')
45
var cls = require('continuation-local-storage')
6+
var microtime = require('../optionalDependencies/microtime')
57

68
var assign = require('lodash.assign')
79

@@ -10,6 +12,10 @@ var Metrics = require('./metrics')
1012
var Healthcheck = require('./healthcheck')
1113
var consts = require('../consts')
1214
var ReservoirSampler = require('./reservoir_sampler')
15+
var Profiler = require('./profiler')
16+
var Control = require('./control')
17+
18+
var controlBus = new EventEmitter()
1319

1420
var REQUEST_ID = 'request-id'
1521
var PARENT_COMM_ID = 'parent-comm-id'
@@ -55,6 +61,18 @@ function Agent (options) {
5561
config: this.config
5662
})
5763

64+
this.memoryProfiler = Profiler.memory.create({
65+
collectorApi: this.collectorApi,
66+
config: this.config,
67+
controlBus: controlBus
68+
})
69+
70+
this.control = Control.create({
71+
collectorApi: this.collectorApi,
72+
config: this.config,
73+
controlBus: controlBus
74+
})
75+
5876
this.collectorApi.getService(function (err, serviceKey) {
5977
if (err) {
6078
return debug(err.message)

lib/agent/index.spec.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ describe('The Trace agent', function () {
3434
responseTime = 100
3535
collectorApi = {
3636
sendSamples: this.sandbox.spy(),
37-
getService: this.sandbox.spy()
37+
getService: this.sandbox.spy(),
38+
getUpdates: this.sandbox.spy()
3839
}
3940
rpmMetrics = {
4041
addResponseTime: this.sandbox.spy(),

0 commit comments

Comments
 (0)