pax_global_header00006660000000000000000000000064120503252610014506gustar00rootroot0000000000000052 comment=078fecfe02bd95fda9d664ea276880261c1c690c statsd-parser-0.0.4/000077500000000000000000000000001205032526100143035ustar00rootroot00000000000000statsd-parser-0.0.4/LICENSE000066400000000000000000000010751205032526100153130ustar00rootroot00000000000000copyright 2012 nuno job (oO)--',-- licensed under the apache license, version 2.0 (the "license"); you may not use this file except in compliance with the license. you may obtain a copy of the license at http://www.apache.org/licenses/LICENSE-2.0 unless required by applicable law or agreed to in writing, software distributed under the license is distributed on an "as is" basis, without warranties or conditions of any kind, either express or implied. see the license for the specific language governing permissions and limitations under the license.statsd-parser-0.0.4/README.md000066400000000000000000000045721205032526100155720ustar00rootroot00000000000000# statsd-parser a streaming parser for the statsd protocol # installation ## node.js 1. install [npm] 2. `npm install statsd-parser` 3. `var statsd_parser = require('statsd-parser');` # usage ## basics ``` js var statsd_parser = require("statsd-parser") , parser = statsd_parser.parser() ; parser.onerror = function (e) { // an error happened. e is the error }; parser.onstat = function (txt, obj) { // got some value }; parser.onend = function () { // parser stream is done, and ready to have more stuff written to it. }; parser.write('foo.bar:1|c').close(); ``` ``` js // // stream usage // takes the same options as the parser // var stream = require("statsd-parser").createStream(options); stream.on("error", function (e) { // unhandled errors will throw, since this is a proper node // event emitter. console.error("error!", e); // clear the error this._parser.error = null; this._parser.resume(); }) stream.on("stat", function (txt, obj) { // same object as above }); // // pipe is supported, and it's readable/writable // same chunks coming in also go out // fs.createReadStream("file.statsd") .pipe(stream) .pipe(fs.createReadStream("file-fixed.statsd")) ``` you also have two helper functions to see if a string is a statsd stat `isStatsd` and one to check extract values from a statsd string (or null if it's not statsd) ``` js > var statsd = require('statsd-parser'); > statsd.isStatsd('a:1|c|@0.1') true > statsd.matchStatsd('a:1|c|@0.1') { stat: 'a', value: '1', type: 'c', sample_rate: '0.1' } > statsd.matchStatsd('b:200|s') { stat: 'b', value: '200', type: 's' } > statsd.matchStatsd('b:200|s@INVALID') null ``` # contribute everyone is welcome to contribute. patches, bug-fixes, new features 1. create an [issue][issues] so the community can comment on your idea 2. fork `statsd-parser` 3. create a new branch `git checkout -b my_branch` 4. create tests for the changes you made 5. make sure you pass both existing and newly inserted tests 6. commit your changes 7. push to your branch `git push origin my_branch` 8. create an pull request # meta * code: `git clone git://github.com/dscape/statsd-parser.git` * home: * bugs: `(oO)--',-` in [caos] [npm]: http://npmjs.org [issues]: http://github.com/dscape/statsd-parser/issues [caos]: http://caos.di.uminho.pt/statsd-parser-0.0.4/index.js000066400000000000000000000154501205032526100157550ustar00rootroot00000000000000var Stream = require('stream').Stream , statsd = exports ; // // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ const ~~ // var EVENTS = ['stat', 'error', 'end', 'ready']; var isStatsD = // // stat :value |type |@sample_rate // // Groups: // * stat : 1 // * value : 2 // * type : 3 // * sample_rate : 5 // /^(.+):([\-+]?[0-9]*\.?[0-9]+)\|(s|g|ms|c)(\|@([\-+]?[0-9]*\.?[0-9]*))?\s*$/; // // Remove error and end for event handling // var F_EVENTS = EVENTS.filter(function (ev) { return ev !== 'error' && ev !== 'end'; }); // // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ stream ~~ // statsd.createStream = function createStream(opt) { return new StatsdStream(opt); }; // // Our streaming parser // function StatsdStream (opt) { // // If `this` ain't already a stream we need to return one with these opts // if (!(this instanceof StatsdStream)) { return statsd.createStream(opt); } var me = this; // // Super call // Stream.apply(me); // // Instantiate our parser // this._parser = new StatsdParser(opt); // // This stream can write and read (duplex) // this.writable = true; this.readable = true; // // Implements the on end function with respect to `this` // this._parser.onend = function () { me.emit('end'); }; // // Implements the on error function with respect to `this` // this._parser.onerror = function (er) { me.emit('error', er); me._parser.error = null; }; // // Define methods on the stream such as // stream.onerror // stream.onstat // // These are defined in F_EVENTS, call on the parser functionality and // are used on the actual `on('stat', f)` handlers // F_EVENTS.forEach(function (ev) { Object.defineProperty(me, 'on' + ev, { get : function () { return me._parser['on' + ev]; } , set : function (h) { if (!h) { me.removeAllListeners(ev); me._parser['on'+ev] = h; return h; } me.on(ev, h); } , enumerable : true , configurable : false }); }); } StatsdStream.prototype = Object.create(Stream.prototype, { constructor: { value: StatsdStream } }); StatsdStream.prototype.write = function (data) { this._parser.write(data.toString()); this.emit('data', data); return true; }; StatsdStream.prototype.end = function (chunk) { if (chunk && chunk.length) { this._parser.write(chunk.toString()); } this._parser.end(); return true; }; StatsdStream.prototype.on = function (ev, handler) { var me = this; // // If we dont have this method in the parser and its included in F_EVENTS // if (!me._parser['on'+ev] && ~F_EVENTS.indexOf(ev)) { // // Define it in the parser // me._parser['on'+ev] = function () { var args = arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments) ; args.splice(0, 0, ev); me.emit.apply(me, args); }; } return Stream.prototype.on.call(me, ev, handler); }; StatsdStream.prototype.destroy = function () { this.emit('close'); }; // // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ parser ~~ // statsd.parser = function (opt) { return new StatsdParser(opt); }; function StatsdParser (opt) { // // If `this` ain't already a parser we need to return one with these opts // if (!(this instanceof StatsdParser)) { return statsd.parser(opt); } var parser = this; // // A buffer for our text until we find newline // parser.buffer = ''; // // Is our parser closed? // parser.closed = false; // // Any possible parser `error` // parser.error = null; // // If we are looking for a new stat // parser.new_stat = true; // mostly just for error reporting parser.position = 0; parser.column = 0; parser.line = 1; emit(parser, 'onready'); } StatsdParser.prototype.end = function end() { end(this); }; StatsdParser.prototype.resume = function resume() { this.error = null; return this; }; StatsdParser.prototype.close = function close() { return this.write(null); }; StatsdParser.prototype.write = function write (chunk) { var parser = this; // // If there is an error // if (this.error) { throw this.error; } // // Cant write, already closed // if (parser.closed) { return error(parser, 'Cannot write after close. Assign an onready handler.'); } // // Nothing to do here // if (chunk === null) { return end(parser); } // // Get ready for some parsing // var i = 0 , c = chunk[0] ; while (c) { c = chunk.charAt(i++); if(parser.new_stat && c === ' ' || c === '\t' || c === '\r') { // // Ignore whitespace // continue; } parser.position++; if (c === '\n') { emitStat(parser, true); parser.line ++; parser.column = 0; parser.new_stat = true; } else { parser.new_stat = false; if (!(c === '\r' || c === '\t')) { parser.buffer += c; } parser.column ++; } } emitStat(parser); return parser; }; statsd.isStatsd = function test(string) { return isStatsD.test(string); }; statsd.matchStatsd = function match(string) { var m = isStatsD.exec(string); if(m) { var stat = { stat : m[1] , value : m[2] , type : m[3] }; if(typeof m[5] === 'string' && m[5] !== '') { stat.sample_rate = m[5]; } return stat; } else { // // Just like in the original exec // return null; } }; // // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ aux ~~ // function emitStat(parser, fromNewline) { if(!parser.buffer || parser.buffer === '') { return; } var stat = statsd.matchStatsd(parser.buffer); if(stat) { emit(parser, 'onstat', parser.buffer, stat); parser.buffer = ''; } else { if(fromNewline) { var buf = parser.buffer; parser.buffer = ''; error(parser, "Buffered Line was not valid stats d `" + buf + "`."); } } } function emit(parser, event, txtData, jsonData) { if (parser[event]) { parser[event](txtData, jsonData); } } function error (parser, er) { // // See if we have anything buffered // emitStat(parser); er += '\nLine: ' + parser.line + '\nColumn: ' + parser.column + '\nBuffer: ' + parser.buffer; er = new Error(er); parser.error = er; emit(parser, 'onerror', er); return parser; } function end(parser) { // // See if we have anything buffered // emitStat(parser); parser.buffer = ''; parser.closed = true; emit(parser, 'onend'); // // Restart our parser // StatsdParser.call(parser, parser.opt); return parser; }statsd-parser-0.0.4/package.json000066400000000000000000000010151205032526100165660ustar00rootroot00000000000000{ "name": "statsd-parser", "version": "0.0.4", "description": "Streaming parser for the statsd protocol", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", "url": "git://github.com/dscape/statsd-parser.git" }, "keywords": [ "statsd", "parser", "streams", "streaming", "stats", "metrics", "lynx" ], "author": "Nuno Job (http://nunojob.com)", "license": "Apache 2" }