Skip to content

Commit

Permalink
feat(metrics): add custom metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
gergelyke committed Aug 16, 2016
1 parent 18b4670 commit fb57943
Show file tree
Hide file tree
Showing 11 changed files with 181 additions and 0 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,34 @@ current logging systems.
var transactionId = trace.getTransactionId();
```

### trace.recordMetric(name, value)

This method can be used to record custom metrics values.

```javascript
trace.recordMetric('user/imageUpload', 6)
```

The name must have the following format: `<Category>/<Name>`
The value must be a number.

### trace.incrementMetric(name, [amount])

This method can be used to record increment-only type of metrics.

```javascript
trace.incrementMetric('user/signup')
```

The name must have the following format: `<Category>/<Name>`

## Compatibility with Node versions

* node v0.10@latest
* node v0.12@latest
* node v4@latest
* node v5@latest
* node v6@latest

## Migrating from 1.x to 2.x

Expand Down
14 changes: 14 additions & 0 deletions lib/agent/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function CollectorApi (options) {
this.COLLECTOR_API_PROFILER_MEMORY_HEAPDUMP = url.resolve(options.collectorApiUrl, options.collectorApiProfilerMemoryHeapdumpEndpoint)
this.COLLECTOR_API_PROFILER_CPU_PROFILE = url.resolve(options.collectorApiUrl, options.collectorApiProfilerCpuProfileEndpoint)
this.COLLECTOR_API_CONTROL = url.resolve(options.collectorApiUrl, options.collectorApiControlEndpoint)
this.COLLECTOR_API_CUSTOM_METRICS = url.resolve(options.collectorApiUrl, options.collectorApiCustomMetrics)

this.collectorLanguage = options.collectorLanguage
this.apiKey = options.apiKey
Expand Down Expand Up @@ -193,6 +194,19 @@ CollectorApi.prototype.getUpdates = function (data, callback) {
})
}

CollectorApi.prototype.sendCustomMetrics = function (data) {
if (!isNumber(this.serviceKey)) {
debug('Service id not present, cannot send metrics')
return
}

var url = util.format(this.COLLECTOR_API_CUSTOM_METRICS, this.serviceKey)
this._send(url, this._withInstanceInfo({
timestamp: (new Date()).toISOString(),
data: data
}))
}

CollectorApi.prototype.sendSamples = function (samples, sync) {
var url = this.COLLECTOR_API_SAMPLE
var metadata = {
Expand Down
1 change: 1 addition & 0 deletions lib/agent/api/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ describe('The Trace CollectorApi module', function () {
collectorApiProfilerMemoryHeapdumpEndpoint: '/service/%s/memory-heapdump',
collectorApiProfilerCpuProfileEndpoint: '/service/%s/cpu-profile',
collectorApiControlEndpoint: '/service/%s/control',
collectorApiCustomMetrics: '/service/%s/custom-metrics',
system: {
hostname: 'test.org',
processVersion: '4.3.1',
Expand Down
6 changes: 6 additions & 0 deletions lib/agent/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ function Agent (options) {
controlBus: controlBus
})

this.customMetrics = Metrics.customMetrics.create({
collectorApi: this.collectorApi,
config: this.config
})

// TODO: The Tracer agent, to be extracted
this.name = 'Tracer'

Expand Down Expand Up @@ -102,6 +107,7 @@ function Agent (options) {
this.rpmMetrics,
this.externalEdgeMetrics,
this.incomingEdgeMetrics,
this.customMetrics,
this.memoryProfiler,
this.cpuProfiler,
this.control
Expand Down
61 changes: 61 additions & 0 deletions lib/agent/metrics/custom/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
var debug = require('debug')('risingstack/trace')

var Timer = require('../../timer')

function CustomMetrics (options) {
this.name = 'Metrics/Custom'
var _this = this
this.collectorApi = options.collectorApi
this.config = options.config
this.collectInterval = this.config.collectInterval

this.incrementMetrics = {}
this.recordMetrics = {}

this.timer = new Timer(function () {
_this.sendMetrics()
}, this.collectInterval)
}

CustomMetrics.prototype.increment = function (name, amount) {
if (!name) {
throw new Error('Name is needed for CustomMetrics.increment')
}
amount = amount || 1
if (this.incrementMetrics[name]) {
this.incrementMetrics[name] += amount
} else {
this.incrementMetrics[name] = amount
}
}

CustomMetrics.prototype.record = function (name, value) {
if (!name) {
throw new Error('Name is needed for CustomMetrics.record')
}
if (typeof value === 'undefined') {
throw new Error('Name is needed for CustomMetrics.record')
}

if (this.recordMetrics[name]) {
this.recordMetrics[name].push(value)
} else {
this.recordMetrics[name] = [value]
}
}

CustomMetrics.prototype.sendMetrics = function () {
this.collectorApi.sendCustomMetrics({
incrementMetrics: this.incrementMetrics,
recordMetrics: this.recordMetrics
})

this.incrementMetrics = {}
this.recordMetrics = {}
}

function create (options) {
return new CustomMetrics(options)
}

module.exports.create = create
41 changes: 41 additions & 0 deletions lib/agent/metrics/custom/index.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
var expect = require('chai').expect

var CustomMetrics = require('./')

describe('The CustomMetrics module', function () {
it('sends metrics', function () {
var ISOString = 'date-string'
var collectorApi = {
sendCustomMetrics: this.sandbox.spy()
}

this.sandbox.stub(Date.prototype, 'toISOString', function () {
return ISOString
})

var customMetrics = CustomMetrics.create({
collectorApi: collectorApi,
config: {
collectInterval: 1
}
})

customMetrics.increment('/TestCategory/TestName')
customMetrics.increment('/TestCategory/TestName')
customMetrics.increment('/TestCategory/TestName', 3)

customMetrics.record('/TestCategory/TestRecord', 10)
customMetrics.record('/TestCategory/TestRecord', 2)

customMetrics.sendMetrics()

expect(collectorApi.sendCustomMetrics).to.be.calledWith({
incrementMetrics: {
'/TestCategory/TestName': 5
},
recordMetrics: {
'/TestCategory/TestRecord': [10, 2]
}
})
})
})
1 change: 1 addition & 0 deletions lib/agent/metrics/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ module.exports.apm = require('./apm')
module.exports.rpm = require('./rpm')
module.exports.incomingEdge = require('./incomingEdge')
module.exports.externalEdge = require('./externalEdge')
module.exports.customMetrics = require('./custom')
1 change: 1 addition & 0 deletions lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ config.collectorApiHealthcheckEndpoint = '/service/%s/healthcheck'
config.collectorApiProfilerMemoryHeapdumpEndpoint = '/service/%s/memory-heapdump'
config.collectorApiProfilerCpuProfileEndpoint = '/service/%s/cpu-profile'
config.collectorApiControlEndpoint = '/service/%s/control'
config.collectorApiCustomMetrics = '/service/%s/custom-metrics'

config.configPath = 'trace.config'

Expand Down
8 changes: 8 additions & 0 deletions lib/trace.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ Trace.prototype.sendMemorySnapshot = function () {
this._agent.memoryProfiler.sendSnapshot()
}

Trace.prototype.incrementMetric = function (name, amount) {
this._agent.customMetrics.increment(name, amount)
}

Trace.prototype.recordMetric = function (name, value) {
this._agent.customMetrics.record(name, value)
}

module.exports.Trace = Trace
module.exports.noop = traceNoop

Expand Down
25 changes: 25 additions & 0 deletions lib/trace.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ describe('Trace', function () {
report: sinon.spy(),
reportError: sinon.spy(),
getRequestId: sinon.spy(),
customMetrics: {
increment: sinon.spy(),
record: sinon.spy()
},
memoryProfiler: { }
}

Expand All @@ -64,6 +68,8 @@ describe('Trace', function () {
fakeAgent.report.reset()
fakeAgent.reportError.reset()
fakeAgent.getRequestId.reset()
fakeAgent.customMetrics.increment.reset()
fakeAgent.customMetrics.record.reset()
})

it('is a constructor', function () {
Expand Down Expand Up @@ -147,6 +153,25 @@ describe('Trace', function () {
expect(fakeAgent.getRequestId).to.have.been.called
})
})

describe('customMetrics', function () {
it('is a function', function () {
this.sandbox.stub(Agent, 'create').returns(fakeAgent)
this.sandbox.stub(Instrumentation, 'create').returns({})
var instance = new trace.Trace(fakeConfig)
expect(instance.incrementMetric).to.be.a('function')
expect(instance.recordMetric).to.be.a('function')
})
it('calls agent.customMetrics', function () {
this.sandbox.stub(Agent, 'create').returns(fakeAgent)
this.sandbox.stub(Instrumentation, 'create').returns({})
var instance = new trace.Trace(fakeConfig)
instance.incrementMetric()
instance.recordMetric()
expect(fakeAgent.customMetrics.record).to.have.been.called
expect(fakeAgent.customMetrics.increment).to.have.been.called
})
})
})
})

Expand Down
1 change: 1 addition & 0 deletions test/e2e/initialization.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ test('should get service key',
t.equal(requestBody.version, '2', 'schema version ok')
t.equal(requestBody.collector.version, pkg.version, 'collector version ok')
t.end()
process.exit(0)
}
})
require('@risingstack/trace')
Expand Down

0 comments on commit fb57943

Please sign in to comment.