-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #29 from tolbertam/16-custom-reporters
ScheduledReporter with console, csv and graphite implementations.
- Loading branch information
Showing
14 changed files
with
646 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
'use strict'; | ||
var ScheduledReporter = require('./scheduled-reporter.js'), | ||
util = require('util'); | ||
|
||
/** | ||
* A custom reporter that prints all metrics on console.log at a defined interval. | ||
* @param {Report} registry report instance whose metrics to report on. | ||
* @constructor | ||
*/ | ||
function ConsoleReporter(registry) { | ||
ConsoleReporter.super_.call(this, registry); | ||
} | ||
|
||
util.inherits(ConsoleReporter, ScheduledReporter); | ||
|
||
ConsoleReporter.prototype.report = function() { | ||
var metrics = this.getMetrics(); | ||
|
||
if(metrics.counters.length != 0) { | ||
printWithBanner('Counters'); | ||
metrics.counters.forEach(function (counter) { | ||
printCounter(counter); | ||
}); | ||
console.log(); | ||
} | ||
|
||
if(metrics.meters.length != 0) { | ||
printWithBanner('Meters'); | ||
metrics.meters.forEach(function (meter) { | ||
printMeter(meter); | ||
}); | ||
console.log(); | ||
} | ||
|
||
if(metrics.timers.length != 0) { | ||
printWithBanner('Timers'); | ||
metrics.timers.forEach(function (timer) { | ||
// Don't log timer if its recorded no metrics. | ||
if(timer.min() != null) { | ||
printTimer(timer); | ||
} | ||
}); | ||
console.log(); | ||
} | ||
}; | ||
|
||
function printWithBanner(name) { | ||
var dashLength = 80 - name.length - 1; | ||
var dashes = ""; | ||
for(var i = 0; i < dashLength; i++) { | ||
dashes += '-'; | ||
} | ||
console.log('%s %s', name, dashes); | ||
} | ||
|
||
function ff(value) { | ||
var fixed = value.toFixed(2); | ||
return fixed >= 10 || fixed < 0 ? fixed : " " + fixed; | ||
} | ||
|
||
function printCounter(counter) { | ||
console.log(counter.name); | ||
console.log(' count = %d', counter.count); | ||
} | ||
|
||
function printMeter(meter) { | ||
console.log(meter.name); | ||
console.log(' count = %d', meter.count); | ||
console.log(' mean rate = %s events/%s', ff(meter.meanRate()), 'second'); | ||
console.log(' 1-minute rate = %s events/%s', ff(meter.oneMinuteRate()), 'second'); | ||
console.log(' 5-minute rate = %s events/%s', ff(meter.fiveMinuteRate()), 'second'); | ||
console.log(' 15-minute rate = %s events/%s', ff(meter.fifteenMinuteRate()), 'second'); | ||
} | ||
|
||
function printTimer(timer) { | ||
console.log(timer.name); | ||
console.log(' count = %d', timer.count()); | ||
console.log(' mean rate = %s events/%s', ff(timer.meanRate()), 'second'); | ||
console.log(' 1-minute rate = %s events/%s', ff(timer.oneMinuteRate()), 'second'); | ||
console.log(' 5-minute rate = %s events/%s', ff(timer.fiveMinuteRate()), 'second'); | ||
console.log(' 15-minute rate = %s events/%s', ff(timer.fifteenMinuteRate()), 'second'); | ||
|
||
var percentiles = timer.percentiles([.50,.75,.95,.98,.99,.999]); | ||
|
||
console.log(' min = %s %s', ff(timer.min()), 'milliseconds'); | ||
console.log(' max = %s %s', ff(timer.max()), 'milliseconds'); | ||
console.log(' mean = %s %s', ff(timer.mean()), 'milliseconds'); | ||
console.log(' stddev = %s %s', ff(timer.stdDev()), 'milliseconds'); | ||
console.log(' 50%% <= %s %s', ff(percentiles[.50]), 'milliseconds'); | ||
console.log(' 75%% <= %s %s', ff(percentiles[.75]), 'milliseconds'); | ||
console.log(' 95%% <= %s %s', ff(percentiles[.95]), 'milliseconds'); | ||
console.log(' 98%% <= %s %s', ff(percentiles[.98]), 'milliseconds'); | ||
console.log(' 99%% <= %s %s', ff(percentiles[.99]), 'milliseconds'); | ||
console.log(' 99.9%% <= %s %s', ff(percentiles[.999]), 'milliseconds'); | ||
} | ||
|
||
module.exports = ConsoleReporter; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
'use strict'; | ||
var ScheduledReporter = require('./scheduled-reporter.js'), | ||
util = require('util'), | ||
fs = require('fs'); | ||
|
||
/** | ||
* A custom reporter that will create a csv file for each metric that is appended to on the defined reporting interval. | ||
* @param {Report} registry report instance whose metrics to report on. | ||
* @param {String} directory Directory to put csv files in. | ||
* @constructor | ||
*/ | ||
function CsvReporter(registry, directory) { | ||
CsvReporter.super_.call(this, registry); | ||
this.directory = directory; | ||
} | ||
|
||
util.inherits(CsvReporter, ScheduledReporter); | ||
|
||
CsvReporter.prototype.report = function() { | ||
var metrics = this.getMetrics(); | ||
var self = this; | ||
|
||
var timestamp = (new Date).getTime() / 1000; | ||
|
||
metrics.counters.forEach(function(counter) { | ||
self.reportCounter.bind(self)(counter, timestamp); | ||
}); | ||
|
||
metrics.meters.forEach(function(meter) { | ||
self.reportMeter.bind(self)(meter, timestamp); | ||
}); | ||
|
||
metrics.timers.forEach(function(timer) { | ||
// Don't log timer if its recorded no metrics. | ||
if(timer.min() != null) { | ||
self.reportTimer.bind(self)(timer, timestamp); | ||
} | ||
}) | ||
}; | ||
|
||
CsvReporter.prototype.write = function(timestamp, name, header, line, values) { | ||
var file = util.format('%s/%s.csv', this.directory, name); | ||
// copy params and add line to the beginning and pass as arguments to util.format | ||
// this operates like a quasi 'vsprintf'. | ||
var params = values.slice(); | ||
params.unshift(line + "\n"); | ||
var data = util.format.apply(util, params); | ||
data = util.format('%d,%s', timestamp, data); | ||
fs.exists(file, function(exists) { | ||
if(!exists) { | ||
// include header if file didn't previously exist | ||
data = util.format("t,%s\n%s", header, data); | ||
} | ||
fs.appendFile(file, data); | ||
}); | ||
}; | ||
|
||
CsvReporter.prototype.reportCounter = function(counter, timestamp) { | ||
var write = this.write.bind(this); | ||
|
||
write(timestamp, counter.name, 'count', '%d', [counter.count]); | ||
}; | ||
|
||
CsvReporter.prototype.reportMeter = function(meter, timestamp) { | ||
var write = this.write.bind(this); | ||
|
||
write(timestamp, meter.name, | ||
'count,mean_rate,m1_rate,m5_rate,m15_rate,rate_unit', | ||
'%d,%d,%d,%d,%d,events/%s', [ | ||
meter.count, | ||
meter.meanRate(), | ||
meter.oneMinuteRate(), | ||
meter.fiveMinuteRate(), | ||
meter.fifteenMinuteRate(), | ||
'second' | ||
] | ||
); | ||
}; | ||
|
||
CsvReporter.prototype.reportTimer = function(timer, timestamp) { | ||
var write = this.write.bind(this); | ||
var percentiles = timer.percentiles([.50,.75,.95,.98,.99,.999]); | ||
|
||
write(timestamp, timer.name, | ||
'count,max,mean,min,stddev,p50,p75,p95,p98,p99,p999,mean_rate,m1_rate,' + | ||
'm5_rate,m15_rate,rate_unit,duration_unit', | ||
'%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,calls/%s,%s', [ | ||
timer.count(), | ||
timer.max(), | ||
timer.mean(), | ||
timer.min(), | ||
timer.stdDev(), | ||
percentiles[.50], | ||
percentiles[.75], | ||
percentiles[.95], | ||
percentiles[.98], | ||
percentiles[.99], | ||
percentiles[.999], | ||
timer.meanRate(), | ||
timer.oneMinuteRate(), | ||
timer.fiveMinuteRate(), | ||
timer.fifteenMinuteRate(), | ||
'second', | ||
'millisecond' | ||
]); | ||
}; | ||
|
||
module.exports = CsvReporter; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
'use strict'; | ||
var ScheduledReporter = require('./scheduled-reporter.js'), | ||
util = require('util'), | ||
Socket = require('net').Socket; | ||
|
||
var reconnecting = false; | ||
|
||
/** | ||
* A custom reporter that sends metrics to a graphite server on the carbon tcp interface. | ||
* @param {Report} registry report instance whose metrics to report on. | ||
* @param {String} prefix A string to prefix on each metric (i.e. app.hostserver) | ||
* @param {String} host The ip or hostname of the target graphite server. | ||
* @param {String} port The port graphite is running on, defaults to 2003 if not specified. | ||
* @constructor | ||
*/ | ||
function GraphiteReporter(registry, prefix, host, port) { | ||
GraphiteReporter.super_.call(this, registry); | ||
this.prefix = prefix; | ||
this.host = host; | ||
this.port = port || 2003; | ||
} | ||
|
||
util.inherits(GraphiteReporter, ScheduledReporter); | ||
|
||
GraphiteReporter.prototype.start = function(intervalInMs) { | ||
var self = this; | ||
this.socket = new Socket(); | ||
this.socket.on('error', function(exc) { | ||
if(!reconnecting) { | ||
reconnecting = true; | ||
self.emit('log', 'warn', util.format('Lost connection to %s. Will reconnect in 10 seconds.', self.host), exc); | ||
// Stop the reporter and try again in 1 second. | ||
self.stop(); | ||
setTimeout(function () { | ||
reconnecting = false; | ||
self.start(intervalInMs); | ||
}, 10000); | ||
} | ||
}); | ||
|
||
self.emit('log', 'verbose', util.format("Connecting to graphite @ %s:%d", this.host, this.port)); | ||
this.socket.connect(this.port, this.host, function() { | ||
self.emit('log', 'verbose', util.format('Successfully connected to graphite @ %s:%d.', self.host, self.port)); | ||
GraphiteReporter.super_.prototype.start.call(self, intervalInMs); | ||
}); | ||
}; | ||
|
||
GraphiteReporter.prototype.stop = function() { | ||
GraphiteReporter.super_.prototype.stop.call(this); | ||
this.socket.end(); | ||
}; | ||
|
||
GraphiteReporter.prototype.report = function() { | ||
// Don't report while reconnecting. | ||
if(reconnecting) { | ||
return; | ||
} | ||
var metrics = this.getMetrics(); | ||
var self = this; | ||
var timestamp = (new Date).getTime() / 1000; | ||
|
||
if(metrics.counters.length != 0) { | ||
metrics.counters.forEach(function (count) { | ||
self.reportCounter.bind(self)(count, timestamp); | ||
}) | ||
} | ||
|
||
if(metrics.meters.length != 0) { | ||
metrics.meters.forEach(function (meter) { | ||
self.reportMeter.bind(self)(meter, timestamp); | ||
}) | ||
} | ||
|
||
if(metrics.timers.length != 0) { | ||
metrics.timers.forEach(function (timer) { | ||
// Don't log timer if its recorded no metrics. | ||
if(timer.min() != null) { | ||
self.reportTimer.bind(self)(timer, timestamp); | ||
} | ||
}) | ||
} | ||
}; | ||
|
||
GraphiteReporter.prototype.send = function(name, value, timestamp) { | ||
if(reconnecting) { | ||
return; | ||
} | ||
this.socket.write(util.format('%s.%s %s %s\n', this.prefix, name, value, | ||
timestamp)); | ||
}; | ||
|
||
GraphiteReporter.prototype.reportCounter = function(counter, timestamp) { | ||
var send = this.send.bind(this); | ||
|
||
send(counter.name, counter.count, timestamp); | ||
}; | ||
|
||
GraphiteReporter.prototype.reportMeter = function(meter, timestamp) { | ||
var send = this.send.bind(this); | ||
|
||
send(util.format('%s.%s', meter.name, 'count'), meter.count, timestamp); | ||
send(util.format('%s.%s', meter.name, 'mean_rate'), meter.meanRate(), timestamp); | ||
send(util.format('%s.%s', meter.name, 'm1_rate'), meter.oneMinuteRate(), | ||
timestamp); | ||
send(util.format('%s.%s', meter.name, 'm5_rate'), meter.fiveMinuteRate(), | ||
timestamp); | ||
send(util.format('%s.%s', meter.name, 'm15_rate'), meter.fifteenMinuteRate(), | ||
timestamp); | ||
}; | ||
|
||
GraphiteReporter.prototype.reportTimer = function(timer, timestamp) { | ||
var send = this.send.bind(this); | ||
send(util.format('%s.%s', timer.name, 'count'), timer.count(), timestamp); | ||
send(util.format('%s.%s', timer.name, 'mean_rate'), timer.meanRate(), timestamp); | ||
send(util.format('%s.%s', timer.name, 'm1_rate'), timer.oneMinuteRate(), | ||
timestamp); | ||
send(util.format('%s.%s', timer.name, 'm5_rate'), timer.fiveMinuteRate(), | ||
timestamp); | ||
send(util.format('%s.%s', timer.name, 'm15_rate'), timer.fifteenMinuteRate(), | ||
timestamp); | ||
|
||
var percentiles = timer.percentiles([.50,.75,.95,.98,.99,.999]); | ||
send(util.format('%s.%s', timer.name, 'min'), timer.min(), timestamp); | ||
send(util.format('%s.%s', timer.name, 'mean'), timer.mean(), timestamp); | ||
send(util.format('%s.%s', timer.name, 'max'), timer.max(), timestamp); | ||
send(util.format('%s.%s', timer.name, 'stddev'), timer.stdDev(), timestamp); | ||
send(util.format('%s.%s', timer.name, 'p50'), percentiles[.50], timestamp); | ||
send(util.format('%s.%s', timer.name, 'p75'), percentiles[.75], timestamp); | ||
send(util.format('%s.%s', timer.name, 'p95'), percentiles[.95], timestamp); | ||
send(util.format('%s.%s', timer.name, 'p98'), percentiles[.98], timestamp); | ||
send(util.format('%s.%s', timer.name, 'p99'), percentiles[.99], timestamp); | ||
send(util.format('%s.%s', timer.name, 'p999'), percentiles[.999], timestamp); | ||
}; | ||
|
||
module.exports = GraphiteReporter; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,6 @@ | ||
exports.Report = require('./report'); | ||
exports.Server = require('./server'); | ||
exports.ScheduledReporter = require('./scheduled-reporter'); | ||
exports.ConsoleReporter = require('./console-reporter'); | ||
exports.CsvReporter = require('./csv-reporter'); | ||
exports.GraphiteReporter = require('./graphite-reporter'); |
Oops, something went wrong.