Skip to content

Commit fb57943

Browse files
committed
feat(metrics): add custom metrics
1 parent 18b4670 commit fb57943

File tree

11 files changed

+181
-0
lines changed

11 files changed

+181
-0
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,34 @@ current logging systems.
109109
var transactionId = trace.getTransactionId();
110110
```
111111

112+
### trace.recordMetric(name, value)
113+
114+
This method can be used to record custom metrics values.
115+
116+
```javascript
117+
trace.recordMetric('user/imageUpload', 6)
118+
```
119+
120+
The name must have the following format: `<Category>/<Name>`
121+
The value must be a number.
122+
123+
### trace.incrementMetric(name, [amount])
124+
125+
This method can be used to record increment-only type of metrics.
126+
127+
```javascript
128+
trace.incrementMetric('user/signup')
129+
```
130+
131+
The name must have the following format: `<Category>/<Name>`
132+
112133
## Compatibility with Node versions
113134

114135
* node v0.10@latest
115136
* node v0.12@latest
116137
* node v4@latest
117138
* node v5@latest
139+
* node v6@latest
118140

119141
## Migrating from 1.x to 2.x
120142

lib/agent/api/index.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ function CollectorApi (options) {
2020
this.COLLECTOR_API_PROFILER_MEMORY_HEAPDUMP = url.resolve(options.collectorApiUrl, options.collectorApiProfilerMemoryHeapdumpEndpoint)
2121
this.COLLECTOR_API_PROFILER_CPU_PROFILE = url.resolve(options.collectorApiUrl, options.collectorApiProfilerCpuProfileEndpoint)
2222
this.COLLECTOR_API_CONTROL = url.resolve(options.collectorApiUrl, options.collectorApiControlEndpoint)
23+
this.COLLECTOR_API_CUSTOM_METRICS = url.resolve(options.collectorApiUrl, options.collectorApiCustomMetrics)
2324

2425
this.collectorLanguage = options.collectorLanguage
2526
this.apiKey = options.apiKey
@@ -193,6 +194,19 @@ CollectorApi.prototype.getUpdates = function (data, callback) {
193194
})
194195
}
195196

197+
CollectorApi.prototype.sendCustomMetrics = function (data) {
198+
if (!isNumber(this.serviceKey)) {
199+
debug('Service id not present, cannot send metrics')
200+
return
201+
}
202+
203+
var url = util.format(this.COLLECTOR_API_CUSTOM_METRICS, this.serviceKey)
204+
this._send(url, this._withInstanceInfo({
205+
timestamp: (new Date()).toISOString(),
206+
data: data
207+
}))
208+
}
209+
196210
CollectorApi.prototype.sendSamples = function (samples, sync) {
197211
var url = this.COLLECTOR_API_SAMPLE
198212
var metadata = {

lib/agent/api/index.spec.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ describe('The Trace CollectorApi module', function () {
2525
collectorApiProfilerMemoryHeapdumpEndpoint: '/service/%s/memory-heapdump',
2626
collectorApiProfilerCpuProfileEndpoint: '/service/%s/cpu-profile',
2727
collectorApiControlEndpoint: '/service/%s/control',
28+
collectorApiCustomMetrics: '/service/%s/custom-metrics',
2829
system: {
2930
hostname: 'test.org',
3031
processVersion: '4.3.1',

lib/agent/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ function Agent (options) {
7474
controlBus: controlBus
7575
})
7676

77+
this.customMetrics = Metrics.customMetrics.create({
78+
collectorApi: this.collectorApi,
79+
config: this.config
80+
})
81+
7782
// TODO: The Tracer agent, to be extracted
7883
this.name = 'Tracer'
7984

@@ -102,6 +107,7 @@ function Agent (options) {
102107
this.rpmMetrics,
103108
this.externalEdgeMetrics,
104109
this.incomingEdgeMetrics,
110+
this.customMetrics,
105111
this.memoryProfiler,
106112
this.cpuProfiler,
107113
this.control

lib/agent/metrics/custom/index.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
var debug = require('debug')('risingstack/trace')
2+
3+
var Timer = require('../../timer')
4+
5+
function CustomMetrics (options) {
6+
this.name = 'Metrics/Custom'
7+
var _this = this
8+
this.collectorApi = options.collectorApi
9+
this.config = options.config
10+
this.collectInterval = this.config.collectInterval
11+
12+
this.incrementMetrics = {}
13+
this.recordMetrics = {}
14+
15+
this.timer = new Timer(function () {
16+
_this.sendMetrics()
17+
}, this.collectInterval)
18+
}
19+
20+
CustomMetrics.prototype.increment = function (name, amount) {
21+
if (!name) {
22+
throw new Error('Name is needed for CustomMetrics.increment')
23+
}
24+
amount = amount || 1
25+
if (this.incrementMetrics[name]) {
26+
this.incrementMetrics[name] += amount
27+
} else {
28+
this.incrementMetrics[name] = amount
29+
}
30+
}
31+
32+
CustomMetrics.prototype.record = function (name, value) {
33+
if (!name) {
34+
throw new Error('Name is needed for CustomMetrics.record')
35+
}
36+
if (typeof value === 'undefined') {
37+
throw new Error('Name is needed for CustomMetrics.record')
38+
}
39+
40+
if (this.recordMetrics[name]) {
41+
this.recordMetrics[name].push(value)
42+
} else {
43+
this.recordMetrics[name] = [value]
44+
}
45+
}
46+
47+
CustomMetrics.prototype.sendMetrics = function () {
48+
this.collectorApi.sendCustomMetrics({
49+
incrementMetrics: this.incrementMetrics,
50+
recordMetrics: this.recordMetrics
51+
})
52+
53+
this.incrementMetrics = {}
54+
this.recordMetrics = {}
55+
}
56+
57+
function create (options) {
58+
return new CustomMetrics(options)
59+
}
60+
61+
module.exports.create = create
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
var expect = require('chai').expect
2+
3+
var CustomMetrics = require('./')
4+
5+
describe('The CustomMetrics module', function () {
6+
it('sends metrics', function () {
7+
var ISOString = 'date-string'
8+
var collectorApi = {
9+
sendCustomMetrics: this.sandbox.spy()
10+
}
11+
12+
this.sandbox.stub(Date.prototype, 'toISOString', function () {
13+
return ISOString
14+
})
15+
16+
var customMetrics = CustomMetrics.create({
17+
collectorApi: collectorApi,
18+
config: {
19+
collectInterval: 1
20+
}
21+
})
22+
23+
customMetrics.increment('/TestCategory/TestName')
24+
customMetrics.increment('/TestCategory/TestName')
25+
customMetrics.increment('/TestCategory/TestName', 3)
26+
27+
customMetrics.record('/TestCategory/TestRecord', 10)
28+
customMetrics.record('/TestCategory/TestRecord', 2)
29+
30+
customMetrics.sendMetrics()
31+
32+
expect(collectorApi.sendCustomMetrics).to.be.calledWith({
33+
incrementMetrics: {
34+
'/TestCategory/TestName': 5
35+
},
36+
recordMetrics: {
37+
'/TestCategory/TestRecord': [10, 2]
38+
}
39+
})
40+
})
41+
})

lib/agent/metrics/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ module.exports.apm = require('./apm')
22
module.exports.rpm = require('./rpm')
33
module.exports.incomingEdge = require('./incomingEdge')
44
module.exports.externalEdge = require('./externalEdge')
5+
module.exports.customMetrics = require('./custom')

lib/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ config.collectorApiHealthcheckEndpoint = '/service/%s/healthcheck'
1717
config.collectorApiProfilerMemoryHeapdumpEndpoint = '/service/%s/memory-heapdump'
1818
config.collectorApiProfilerCpuProfileEndpoint = '/service/%s/cpu-profile'
1919
config.collectorApiControlEndpoint = '/service/%s/control'
20+
config.collectorApiCustomMetrics = '/service/%s/custom-metrics'
2021

2122
config.configPath = 'trace.config'
2223

lib/trace.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ Trace.prototype.sendMemorySnapshot = function () {
5252
this._agent.memoryProfiler.sendSnapshot()
5353
}
5454

55+
Trace.prototype.incrementMetric = function (name, amount) {
56+
this._agent.customMetrics.increment(name, amount)
57+
}
58+
59+
Trace.prototype.recordMetric = function (name, value) {
60+
this._agent.customMetrics.record(name, value)
61+
}
62+
5563
module.exports.Trace = Trace
5664
module.exports.noop = traceNoop
5765

lib/trace.spec.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ describe('Trace', function () {
5656
report: sinon.spy(),
5757
reportError: sinon.spy(),
5858
getRequestId: sinon.spy(),
59+
customMetrics: {
60+
increment: sinon.spy(),
61+
record: sinon.spy()
62+
},
5963
memoryProfiler: { }
6064
}
6165

@@ -64,6 +68,8 @@ describe('Trace', function () {
6468
fakeAgent.report.reset()
6569
fakeAgent.reportError.reset()
6670
fakeAgent.getRequestId.reset()
71+
fakeAgent.customMetrics.increment.reset()
72+
fakeAgent.customMetrics.record.reset()
6773
})
6874

6975
it('is a constructor', function () {
@@ -147,6 +153,25 @@ describe('Trace', function () {
147153
expect(fakeAgent.getRequestId).to.have.been.called
148154
})
149155
})
156+
157+
describe('customMetrics', function () {
158+
it('is a function', function () {
159+
this.sandbox.stub(Agent, 'create').returns(fakeAgent)
160+
this.sandbox.stub(Instrumentation, 'create').returns({})
161+
var instance = new trace.Trace(fakeConfig)
162+
expect(instance.incrementMetric).to.be.a('function')
163+
expect(instance.recordMetric).to.be.a('function')
164+
})
165+
it('calls agent.customMetrics', function () {
166+
this.sandbox.stub(Agent, 'create').returns(fakeAgent)
167+
this.sandbox.stub(Instrumentation, 'create').returns({})
168+
var instance = new trace.Trace(fakeConfig)
169+
instance.incrementMetric()
170+
instance.recordMetric()
171+
expect(fakeAgent.customMetrics.record).to.have.been.called
172+
expect(fakeAgent.customMetrics.increment).to.have.been.called
173+
})
174+
})
150175
})
151176
})
152177

0 commit comments

Comments
 (0)