diff --git a/.gitignore b/.gitignore index 5afd8e3..9253f06 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,5 @@ node_modules .idea/ .coverrun reports/ +.coverdata/ +debug/ diff --git a/README.md b/README.md index a54c452..da67eab 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,13 @@ Experimental/Unstable ```js var graphite = require('graphite-promise'), -client = graphite.createClient(); +client = graphite.createClient(); client.write(metric, timestamp) .then(function(){}) .catch(function(reason){}) - .finally(function(){}); + .finally(function(){ + client.end(); + }); ``` ### Example diff --git a/lib/carbon.js b/lib/carbon.js index a1706b1..146cf3b 100644 --- a/lib/carbon.js +++ b/lib/carbon.js @@ -4,57 +4,53 @@ var RSVP = require('rsvp'), function CarbonClient (properties) { properties = properties || {}; - this._dsn = properties.dsn; + this._url = properties.url; this._socket = properties.socket || null; } CarbonClient.prototype.write = function (metrics, timestamp) { var self = this; - self._lazyConnect().then(function () { - }); return new RSVP.Promise(function (resolve, reject) { - var lines = ''; - for (var path in metrics) { - if (metrics.hasOwnProperty(path)) { - var value = metrics[path]; - lines += [path, value, timestamp].join(' ') + '\n'; + self._connect().then(function (socket) { + var lines = ''; + for (var path in metrics) { + if (metrics.hasOwnProperty(path)) { + var value = metrics[path]; + lines += [path, value, timestamp].join(' ') + '\n'; + } } - } - - self._socket.write(lines, 'utf-8', function (err) { - if (err) { - reject(err); - } else { - resolve(metrics); - } - }); + socket.write(lines, 'utf-8', function (err) { + return err && reject(err) || resolve(lines); + }); + }).catch(reject); }); }; -CarbonClient.prototype._lazyConnect = function () { +CarbonClient.prototype._connect = function () { var self = this; return new RSVP.Promise(function (resolve, reject) { if (self._socket) { - return resolve(); + return resolve(self._socket); } - var dsn = url.parse(self._dsn), + var dsn = url.parse(self._url), port = parseInt(dsn.port, 10) || 2003, host = dsn.hostname, socket = new net.Socket(); self._socket = socket.connect(port, host, 1000, function (err) { - if (err) { - reject(err); - } else { - resolve(); - } + return err && reject(err) || resolve(self._socket); }); }); }; CarbonClient.prototype.end = function () { - if (this._socket) { - this._socket.end(); - } + return new RSVP.Promise(function (resolve, reject) { + try { + resolve(this._socket && this._socket.end()); + } + catch (err) { + reject(err); + } + }); }; module.exports = CarbonClient; diff --git a/lib/graphite.js b/lib/graphite.js index f8355b7..30e4615 100644 --- a/lib/graphite.js +++ b/lib/graphite.js @@ -1,4 +1,5 @@ -var CarbonClient = require('./carbon'); +var client = require('./carbon'), + metric = require('./metric'); function GraphiteClient (properties) { this._carbon = properties.carbon; @@ -6,36 +7,28 @@ function GraphiteClient (properties) { GraphiteClient.createClient = function (carbonDsn) { return new this({ - carbon: new CarbonClient({dsn: carbonDsn}) + carbon: new client({url: carbonDsn}) }); }; -GraphiteClient.flatten = function (obj, flat, prefix) { - flat = flat || {}; - prefix = prefix || ''; - - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - var value = obj[key]; - if (typeof value === 'object') { - this.flatten(value, flat, prefix + key + '.'); - } else { - flat[prefix + key] = value; - } - } - } - - return flat; -}; - +/** + * Writes metric with timestamp to Graphite + * + * @param metrics an object with values, e.g. {home:{indoor:{temp:21.2}}} + * @param timestamp defaults to Date.now() + * @returns {RSVP.Promise} a promise + */ GraphiteClient.prototype.write = function (metrics, timestamp) { timestamp = timestamp || Date.now(); timestamp = Math.floor(timestamp / 1000); - return this._carbon.write(GraphiteClient.flatten(metrics), timestamp); + return this._carbon.write(metric.flatten(metrics), timestamp); }; +/** + * Ends the connection + */ GraphiteClient.prototype.end = function () { - this._carbon.end(); + return this._carbon.end(); }; module.exports = GraphiteClient; diff --git a/lib/metric.js b/lib/metric.js new file mode 100644 index 0000000..f1bea83 --- /dev/null +++ b/lib/metric.js @@ -0,0 +1,18 @@ +module.exports = { + flatten: function (obj, flat, prefix) { + flat = flat || {}; + prefix = prefix || ''; + + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + var value = obj[key]; + if (typeof value === 'object') { + this.flatten(value, flat, prefix + key + '.'); + } else { + flat[prefix + key] = value; + } + } + } + return flat; + } +}; diff --git a/package.json b/package.json index b147bfc..19b7d5c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "graphite-promise", - "version": "0.0.1", + "version": "0.0.2", "description": "A node.js module to interface with Graphite promise style", "main": "./lib/graphite", "directories": { diff --git a/test/carbon.js b/test/carbon.js index e69de29..3c85a36 100644 --- a/test/carbon.js +++ b/test/carbon.js @@ -0,0 +1,37 @@ +/*jshint undef:false */ +var chaiAsPromised = require('chai-as-promised'), + chai = require('chai'); + +chai.should(); +chai.use(chaiAsPromised); + +describe('CarbonClient', function () { + var Carbon = require('./../lib/carbon'), + client = new Carbon({dsn: 'plaintext://127.0.0.1:2003/'}), + socketMock = {}; + + describe('#write', function () { + before(function () { + client._socket = socketMock; + }); + + it('writes flattened metrics encoded as utf-8', function (done) { + var metric = {'home.indoor.temp': 21.2}, + timestamp = 1427727486200; + socketMock.write = function (lines, encoding, cb) { + lines.should.equal('home.indoor.temp 21.2 1427727486200\n'); + encoding.should.equal('utf-8'); + cb(); + }; + client.write(metric, timestamp).should.eventually.equal('home.indoor.temp 21.2 1427727486200\n').notify(done); + }); + it('rejects when a socket error occur', function (done) { + var metric = {'home.indoor.temp': 21.2}, + timestamp = 1427727486200; + socketMock.write = function (lines, encoding, cb) { + cb(new Error('fail')); + }; + client.write(metric, timestamp).should.eventually.be.rejected.notify(done); + }); + }); +}); diff --git a/test/graphite.js b/test/graphite.js index e92d13a..0cd7bc6 100644 --- a/test/graphite.js +++ b/test/graphite.js @@ -6,6 +6,11 @@ chai.should(); chai.use(chaiAsPromised); describe('GraphiteClient', function () { - describe('#write', function () { + describe('#end', function () { + it('should be safe to call any time', function (done) { + var graphite = require('./../lib/graphite'), + client = graphite.createClient('plaintext://127.0.0.1:2003/'); + client.end().should.be.fulfilled.notify(done); + }); }); }); diff --git a/test/index.js b/test/index.js index 8756e4e..b0b5675 100644 --- a/test/index.js +++ b/test/index.js @@ -1,2 +1,3 @@ require('./carbon'); -require('./graphite'); \ No newline at end of file +require('./graphite'); +require('./metric'); \ No newline at end of file diff --git a/test/metric.js b/test/metric.js new file mode 100644 index 0000000..fd9c94c --- /dev/null +++ b/test/metric.js @@ -0,0 +1,29 @@ +/*jshint undef:false */ +var chaiAsPromised = require('chai-as-promised'), + chai = require('chai'), + metric = require('./../lib/metric'); + +chai.should(); +chai.use(chaiAsPromised); + +describe('metric', function () { + describe('#flatten', function () { + it('flattens objects', function () { + metric.flatten({home: {indoor: {temp: 21.2}}}).should.deep.equal({'home.indoor.temp': 21.2}); + }); + it('flattens objects two properties', function () { + metric.flatten({home: {indoor: {temp: 21.2, humidity: 94.5}}}) + .should.deep.equal({'home.indoor.humidity': 94.5, 'home.indoor.temp': 21.2}); + }); + it('flattens objects three properties', function () { + metric.flatten({foz: {baz: {foo: 42, bar: 1337, soz: 101}}}) + .should.deep.equal({'foz.baz.bar': 1337, 'foz.baz.foo': 42, 'foz.baz.soz': 101}); + }); + it('handles empty objects', function () { + metric.flatten({}).should.deep.equal({}); + }); + it('handles shallow objects', function () { + metric.flatten({temp: 27.1}).should.deep.equal({temp: 27.1}); + }); + }); +});