pax_global_header00006660000000000000000000000064123632470460014521gustar00rootroot0000000000000052 comment=988c532421b9c5df48efe29653ad74792f5d4c65 lynx-0.2.1/000077500000000000000000000000001236324704600125135ustar00rootroot00000000000000lynx-0.2.1/.gitignore000066400000000000000000000000441236324704600145010ustar00rootroot00000000000000.DS_Store *tmp.* *.log node_modules/lynx-0.2.1/.npmignore000066400000000000000000000000061236324704600145060ustar00rootroot00000000000000.git/ lynx-0.2.1/.travis.yml000066400000000000000000000001451236324704600146240ustar00rootroot00000000000000language: "node_js" branches: only: - master node_js: - 0.4 - 0.6 - 0.8 - 0.9 - 0.10 lynx-0.2.1/CONTRIBUTING.md000066400000000000000000000021701236324704600147440ustar00rootroot00000000000000# Contributing Everyone is welcome to contribute with patches, bug-fixes and new features 1. Create an [issue][2] on github so the community can comment on your idea 2. Fork `lynx` in github 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 a pull request ## Tests Tests are written in `node-tap`. If you want to create a new test create a file named `my-test-name-test.js` under the `tests` directory and it will be automatically picked up by `tap`. Before you do that however please check if a test doesn't already exist. Tests run against a udp ""server"", which is on `tests/macros.js`. If your test name is `foo-bar` this helper function will read a fixtures located in `tests/fixtures/foo-bar.json`. Each fixture is an array containing strings, the strings that we expect the client to send when we issue certain commands. Check `tests/counting-test.js` and `tests/fixtures/counting.js` for an example [2]: http://github.com/dscape/lynx/issueslynx-0.2.1/LICENSE000066400000000000000000000021701236324704600135200ustar00rootroot00000000000000MIT License Copyright 2012 Lloyd Hilaiel, Nuno Job. All rights reserved. Copyright 2011 Steve Ivy. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.lynx-0.2.1/README.md000066400000000000000000000124731236324704600140010ustar00rootroot00000000000000# lynx ![NPM Downloads](http://img.shields.io/npm/dm/lynx.svg?style=flat) ![NPM Version](http://img.shields.io/npm/v/lynx.svg?style=flat) A minimalistic node.js client for [statsd] server. Fork of original work by [sivy] `lynx` features: * **Minimalistic** — there is only a minimum of abstraction between you and statsd * **Streams** — You can stream in and out of a `lynx` connection * **Re-usable UDP Connections** — Keeps UDP connections open for a certain time * **Errors** — Pluggable error handling, by default errors are ignored ## Quick Start ``` $ npm install lynx $ node > var lynx = require('lynx'); // // Options in this instantiation include: // * `on_error` function to be executed when we have errors // * `socket` if you wish to just use a existing udp socket // * `scope` to define the a prefix for all stats, e.g. with `scope` // 'product1' and stat 'somestat' the key would actually be // 'product1.somestat' // > var metrics = new lynx('localhost', 8125); { host: 'localhost', port: 8125 } > metrics.increment('node_test.int'); > metrics.decrement('node_test.int'); > metrics.timing('node_test.some_service.task.time', 500); // time in ms > metrics.gauge('gauge.one', 100); > metrics.set('set.one', 10); ``` This is the equivalent to: ``` sh echo "node_test.int:1|c" | nc -w 0 -u localhost 8125 echo "node_test.int:-1|c" | nc -w 0 -u localhost 8125 echo "node_test.some_service.task.time:500|ms" | nc -w 0 -u localhost 8125 echo "gauge.one:100|g" | nc -w 0 -u localhost 8125 echo "set.one:10|s" | nc -w 0 -u localhost 8125 ``` The protocol is super simple, so feel free to check out the source code to understand how everything works. ## Advanced ### Sampling If you want to track something that happens really, really frequently, it can overwhelm StatsD with UDP packets. To work around that, use the optional sampling rate for metrics. This will only send packets a certain percentage of time. For very frequent events, this will give you a statistically accurate representation of your data. Sample rate is an optional parameter to all of the metric API calls. A valid sample rate is 0.0 - 1.0. Values of 0.0 will never send any packets, and values of 1.0 will send every packet. In these examples we are samping at a rate of 0.1, meaning 1-in-10 calls to send a sample will actually be sent to StatsD. ``` var metrics = new lynx('localhost', 8125); metrics.increment('node_test.int', 0.1); metrics.decrement('node_test.int', 0.1); metrics.timing('node_test.some_service.task.time', 500, 0.1); metrics.gauge('gauge.one', 100, 0.1); metrics.set('set.one', 10, 0.1); var timer2 = metrics.createTimer('node_test.some_service.task2.time', 0.1); timer2.stop(); ``` ### Streams You can stream to `lynx`: ``` js fs.createReadStream('file.statsd') .pipe(new lynx('localhost', port)) .pipe(fs.createReadStream('file-fixed.statsd')) ; ``` Feel free to check the `stream-test` for more info. ### Timers If you wish to measure timing you can use the `timer()` functionality. ``` js var metrics = new lynx('localhost', 8125) , timer = metrics.createTimer('some.interval') ; // // Should send something like "some.interval:100|ms" // setTimeout(function () { timer.stop(); }, 100); ``` Timers use `Date.getTime()` which is known for being imprecise at the ms level. If this is a problem to you please submit a pull request and I'll take it. ### Batching Batching is possible for `increment`, `decrement`, and count: ``` js metrics.decrement(['uno', 'two', 'trezentos']); ``` If you want to mix more than one type of metrics in a single packet you can use `send`, however you need to construct the values yourself. An example: ``` js // // This code is only to exemplify the functionality // // As of the current implementation the sample rate is processed per group // of stats and not per individual stat, meaning either all would be send // or none would be sent. // metrics.send( { "foo" : "-1|c" // count , "bar" : "15|g" // gauge , "baz" : "500|ms" // timing , "boaz": "40|s" // set }, 0.1); // sample rate at `0.1` ``` ### Closing your socket You can close your open socket when you no longer need it by using `metrics.close()`. ### Errors By default `errors` get logged. If you wish to change this behavior simply specify a `on_error` function when instantiating the `lynx` client. ``` js function on_error(err) { console.log(err.message); } var connection = new lynx('localhost', 1234, {on_error: on_error}); ``` Source code is super minimal, if you want try to get familiar with when errors occur check it out. If you would like to change behavior on how this is handled send a pull request justifying why and including the alterations you would like to propose. ## Tests Run the tests with `npm`. ``` sh npm test ``` ## Meta `\. ,/' |\\____//| )/_ `' _\( ,'/-`__'-\`\ /. (_><_) ,\ ` )/`--'\(`' atc ` ' * travis: [![build status](https://secure.travis-ci.org/dscape/lynx.png)](http://travis-ci.org/dscape/lynx) * code: `git clone git://github.com/dscape/lynx.git` * home: * bugs: `(oo)--',-` in [caos] [caos]: http://caos.di.uminho.pt [sivy]: https://github.com/sivy/node-statsd [statsd]: https://github.com/etsy/statsd lynx-0.2.1/lib/000077500000000000000000000000001236324704600132615ustar00rootroot00000000000000lynx-0.2.1/lib/lynx.js000066400000000000000000000404271236324704600146200ustar00rootroot00000000000000var dgram = require('dgram') , Stream = require('stream').Stream , util = require('util') , parser = require('statsd-parser') // // `Math.random` doesn't cut it, based on tests from sampling.js // Variations are wild for large data sets // , mersenne = require('mersenne') , mt = new mersenne.MersenneTwister19937() , noop = function noop() {} ; function makeError(opts) { var error = new Error(opts.message); error.f = opts.f; error.args = opts.args; return error; } // // Max idle time for a ephemeral socket // var EPHEMERAL_LIFETIME_MS = 1000; // // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ constructors ~~ // // // ### constructor Lynx(host, port, socket) // #### @host {String} Server host name // #### @port {Number} Server port // #### @options {Object} Aditional options // #### @options.socket {Object} Optional socket if we want to share // #### @options.on_error {Function} A function to execute on errors // #### @options.scope {String} define the a prefix for all stats, // e.g. with `scope` 'product1' and stat 'somestat' the key would // actually be 'product1.somestat'. // // var client = new lynx('localhost', 8125); // // Returns a new `Lynx` client // function Lynx(host, port, options) { if (!(this instanceof Lynx)) { return new Lynx(host, port, options); } var self = this; // // Server hostname and port // this.host = host || '127.0.0.1'; this.port = port || 8125; // // Optional shared socket // this.socket = options && options.socket; // // Handle prefix // this.scope = options && options.scope || options && options.prefix || ''; // // groups in graphite are delimited by `.` so we need to make sure our // scope ends with `.`. If it doesn't we just add it (unless we have no // scope defined). // if(typeof this.scope === 'string' && this.scope !== '' && !/\.$/.test(this.scope)) { this.scope += '.'; } // // When a *shared* socked isn't provided, an ephemeral // socket is demand allocated. This ephemeral socket is closed // after being idle for EPHEMERAL_LIFETIME_MS. // this.ephemeral_socket = undefined; this.last_used_timer = undefined; // // Set out error handling code // this.on_error = options && typeof options.on_error === 'function' ? options.on_error : this._default_error_handler ; // // Stream properties // this.readable = true; this.writable = true; this.parser = parser.createStream(); this.parser.on('error', this.on_error); this.parser.on('stat', function (text, stat_obj) { var stat = {}; // // Construct a statsd value|type pair // stat[stat_obj.stat] = stat_obj.value + '|' + stat_obj.type; // // Add sample rate if one exists // if(stat_obj.sample_rate) { stat[stat_obj.stat] += '@' + stat_obj.sample_rate; self.send(stat, parseFloat(stat_obj.sample_rate)); } else { self.send(stat); } }); } util.inherits(Lynx, Stream); // // ### constructor Timer(stat, sample_rate) // #### @stat {String} Stat key, in `foo:1|ms` would be foo // #### @sample_rate {Number} Determines the sampling rate, e.g. how many // packets should be sent. If set to 0.1 it sends 1 in each 10. // // var client = new lynx('localhost', 8125); // var timer = client.Timer('foo'); // // // // // Sends something like: `foo:100|ms` via udp to the server // // // setTimeout(function { // timer.stop(); // }, 100); // // Returns a timer. When stopped, this transmits an interval // Lynx.prototype.createTimer = function createTimer(stat, sample_rate) { var self = this , start_time = new Date ().getTime() , stopped = false , duration , start_hrtime ; if (typeof process.hrtime === "function") { var start_hrtime = process.hrtime(); } // // ### function stop() // // Stops the timer and issues the respective interval. // Check example above // function stop() { // // If timer is already stopped just ignore the request // if(stopped) { self.on_error( makeError({ message : "Can't stop a timer twice" , f : 'stop' })); return; } // // Calculate duration // if (start_hrtime) { var stop_hrtime = process.hrtime() , seconds = stop_hrtime[0] - start_hrtime[0] , nanos = stop_hrtime[1] - start_hrtime[1] ; duration = seconds * 1000 + nanos / 1000000 } else { duration = new Date ().getTime() - start_time; } // // Emit // self.timing(stat, duration, sample_rate); // // So no one stops a timer twice (causing two emits) // stopped = true; } // // The closure that is returned // return { stat : stat , sample_rate : sample_rate , start_time : start_time , stop : stop }; }; // // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ api ~~ // // // ### function increment(stats, sample_rate) // #### @stats {String|Array} Stat key, in `foo:1|ms` would be foo // Optionally an array of `stats`. // #### @sample_rate {Number} Determines the sampling rate, e.g. how many // packets should be sent. If set to 0.1 it sends 1 in each 10. // // var client = new lynx('localhost', 8125); // client.increment('getho'); // client.increment(['not', 'cool']); // // Incremenents the desired stat(s) // Lynx.prototype.increment = function increment(stats, sample_rate) { this.count(stats, 1, sample_rate); }; // // ### function decrement(stats, sample_rate) // #### @stats {String|Array} Stat key, in `foo:1|ms` would be foo // Optionally an array of `stats`. // #### @sample_rate {Number} Determines the sampling rate, e.g. how many // packets should be sent. If set to 0.1 it sends 1 in each 10. // // var client = new lynx('localhost', 8125); // client.decrement('hey.you'); // // Decrements the desired stat(s) // Lynx.prototype.decrement = function decrement(stats, sample_rate) { this.count(stats, -1, sample_rate); }; // // ### function count(stats, delta, sample_rate) // #### @stats {String|Array} Stat key, in `foo:1|ms` would be foo // Optionally an array of `stats`. // #### @delta {Number} Amount to add (or remove) from given stat // #### @sample_rate {Number} Determines the sampling rate, e.g. how many // packets should be sent. If set to 0.1 it sends 1 in each 10. // // var client = new lynx('localhost', 8125); // client.count('python.fun', -100); // // Sends counting information to statsd. Normally this is invoked via // `increment` or `decrement` // Lynx.prototype.count = function count(stats, delta, sample_rate) { // // If we are given a string stat (key) then transform it into array // if (typeof stats === 'string') { stats = [stats]; } // // By now stats must be an array // if(!Array.isArray(stats)) { // // Error: Can't set if its not even an array by now // this.on_error( makeError({ message : "Can't set if its not even an array by now" , f : 'count' , args : arguments })); return; } // // Delta is required and must exist or we will send crap to statsd // if (typeof delta!=='number' && typeof delta!=='string' || isNaN(delta)) { // // Error: Must be either a number or a string, we cant send other stuff // this.on_error( makeError({ message : 'Must be either a number or a string' , f : 'count' , args : arguments })); return; } // // Batch up all these stats to send // var batch = {}; for(var i in stats) { batch[stats[i]] = delta + '|c'; } // // Send all these stats // this.send(batch, sample_rate); }; // // ### function timing(stat, duration, sample_rate) // #### @stat {String} Stat key, in `foo:1|ms` would be foo // #### @duration {Number} Timing duration in ms. // #### @sample_rate {Number} Determines the sampling rate, e.g. how many // packets should be sent. If set to 0.1 it sends 1 in each 10. // // var client = new lynx('localhost', 8125); // client.timing('foo.bar.time', 500); // // Sends timing information for a given stat. // Lynx.prototype.timing = function timing(stat, duration, sample_rate) { var stats = {}; stats[stat] = duration + '|ms'; this.send(stats, sample_rate); }; // // ### function set(stat, value, sample_rate) // #### @stat {String} Stat key, in `foo:1|s` would be foo // #### @value {Number} Value for this set // #### @sample_rate {Number} Determines the sampling rate, e.g. how many // packets should be sent. If set to 0.1 it sends 1 in each 10. // // var client = new lynx('localhost', 8125); // client.set('set1.bar', 567); // // Set for a specific stat // Lynx.prototype.set = function set(stat, value, sample_rate) { var stats = {}; stats[stat] = value + '|s'; this.send(stats, sample_rate); }; // // ### function gauge(stat, value, sample_rate) // #### @stat {String} Stat key, in `foo:1|g` would be foo // #### @value {Number} Value for this set // #### @sample_rate {Number} Determines the sampling rate, e.g. how many // packets should be sent. If set to 0.1 it sends 1 in each 10. // // var client = new lynx('localhost', 8125); // client.gauge('gauge1.bar', 567); // // Send a gauge to statsd // Lynx.prototype.gauge = function gauge(stat, value, sample_rate) { var stats = {}; stats[stat] = value + '|g'; this.send(stats, sample_rate); }; // // ### function send(stats, sample_rate) // #### @stats {Object} A stats object // #### @sample_rate {Number} Determines the sampling rate, e.g. how many // packets should be sent. If set to 0.1 it sends 1 in each 10. // // var lynx = require('lynx'); // var client = new lynx('localhost', 8125); // client.send( // { "foo" : "1|c" // , "bar" : "-1|c" // , "baz" : "500|ms" // }); // // Will sample this data for a given sample_rate. If a random generated // number matches that sample_rate then stats get returned and the sample // rate gets appended ("|@0.5" in this case). Else we get an empty object. // Lynx.prototype.send = function send(stats, sample_rate) { var self = this , sampled_stats = Lynx.sample(stats, sample_rate) , all_stats = Object.keys(sampled_stats) // // Data to be sent // , send_data ; // // If this object is empty (enumerable properties) // if(all_stats.length === 0) { // // Error: Nothing to send // this.on_error( makeError({ message : 'Nothing to send' , f : 'send' , args : arguments })); return; } // // Construct our send request // If we have multiple stats send them in the same udp package // This is achieved by having newline separated stats. // send_data = all_stats.map(function construct_stat(stat) { return self.scope + stat + ':' + sampled_stats[stat]; }).join('\n'); // // Encode our data to a buffer // var buffer = new Buffer(send_data, 'utf8') , socket ; // // Do we already have a socket object we can use? // if (this.socket === undefined) { // // Do we have an ephemeral socket we can use? // if (!this.ephemeral_socket) { // // Create one // this.ephemeral_socket = dgram.createSocket('udp4'); // // Register on error: Failed sending the buffer // this.ephemeral_socket.on('error', function (err) { err.reason = err.message; err.f = 'send'; err.message = 'Failed sending the buffer'; err.args = arguments; self.on_error(err); return; }); } socket = this.ephemeral_socket; } else { // // Reuse our socket // socket = this.socket; } // // Update the last time this socket was used // This is used to make the socket ephemeral // this._update_last_used(); // // Send the data // this.emit('data', buffer); socket.send(buffer, 0, buffer.length, this.port, this.host, noop); }; // // ### function close() // // var client = new lynx('localhost', 8125); // client.increment("zigzag"); // client.close(); // // Closes our socket object after we are done with it // Lynx.prototype.close = function close() { // // User defined socket // if (this.socket) { this.socket.close(); this.socket = undefined; } // // Ephemeral socket // if (this.ephemeral_socket) { this.ephemeral_socket.close(); this.ephemeral_socket = undefined; } // // Timer // if (this.last_used_timer) { clearTimeout(this.last_used_timer); this.last_used_timer = undefined; } }; // // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ streams ~~ // // // ### function write() // // Implements `Stream.prototype.write()`. // Lynx.prototype.write = function write(buffer) { this.parser.write(buffer); }; // // ### function end() // // Implements `Stream.prototype.end()`. // Lynx.prototype.end = function end(buffer) { // // If there's stuff to flush please do // if (arguments.length) { this.write(buffer); } // // Make this not writable // this.writable = false; }; // // ### function destroy() // // Implements `Stream.prototype.destroy()`. Nothing to do here, we don't // open any stuff // Lynx.prototype.destroy = function destroy() { this.writable = false; }; // // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ aux ~~ // // // ### function sample(stats, sample_rate) // #### @stats {Object} A stats object // #### @sample_rate {Number} Determines the sampling rate, e.g. how many // packets should be sent. If set to 0.1 it sends 1 in each 10. // // var lynx = require('lynx'); // lynx.sample( // { "foo" : "1|c" // , "bar" : "-1|c" // , "baz" : "500|ms" // }, 0.5); // // Will sample this data for a given sample_rate. If a random generated // number matches that sample_rate then stats get returned and the sample // rate gets appended ("|@0.5" in this case). Else we get an empty object. // Lynx.sample = function sample(stats, sample_rate) { // // If we don't have a sample rate between 0 and 1 // if (typeof sample_rate !== 'number' || sample_rate > 1 || sample_rate < 0) { // // Had to ignore the invalid sample rate // Most of the times this is because sample_rate is undefined // return stats; } var sampled_stats = {}; // // Randomly determine if we should sample this specific instance // if (mt.genrand_real2(0,1) <= sample_rate) { // // Note: Current implementation either sends all stats for a specific // sample rate or sends none. Makes one wonder if granularity // should be at the individual stat level // Object.keys(stats).forEach(function construct_sampled(stat) { var value = stats[stat]; sampled_stats[stat] = value + '|@' + sample_rate; }); } return sampled_stats; }; // // ### function _update_last_used() // // An internal function update the last time the socket was // used. This function is called when the socket is used // and causes demand allocated ephemeral sockets to be closed // after a period of inactivity. // Lynx.prototype._update_last_used = function _update_last_used() { var self = this; // // Only update on the ephemeral socket // if (this.ephemeral_socket) { // // Clear existing timeouts // if (this.last_used_timer) { clearTimeout(this.last_used_timer); } // // Update last_used_timer // this.last_used_timer = setTimeout(function() { // // If we have an open socket close it // if (self.ephemeral_socket) { self.ephemeral_socket.close(); } // // Delete the socket // delete self.ephemeral_socket; }, EPHEMERAL_LIFETIME_MS); } }; // // ### function default_error_handler() // #### @err {Object} The error object. Includes: // err.message, err.* // // Function that defines what to do on error. // Errors are soft errors, and while interesting they are mostly informative // A simple console log would do but that doesn't allow people to do // custom stuff with errors // Lynx.prototype._default_error_handler = function _default_error_handler(e) { this.emit('error', e); }; // // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ exports ~~ // module.exports = Lynx; lynx-0.2.1/package.json000066400000000000000000000016621236324704600150060ustar00rootroot00000000000000{ "name": "lynx", "description": "Minimalistic StatsD client for Node.js programs", "version": "0.2.1", "author": "Lloyd Hilaiel", "contributors": [ "Nuno Job (http://nunojob.com)", "Mark Bogdanoff (https://github.com/bog)" ], "scripts": { "test": "tap tests/*-test.js" }, "repository": { "type": "git", "url": "git://github.com/dscape/lynx.git" }, "bugs": { "web": "https://github.com/dscape/lynx/issues" }, "directories": { "lib": "./lib/" }, "main": "./lib/lynx", "keywords": [ "stats", "metrics", "metricsd", "statsd", "etsy", "statsd client", "statsd driver", "graphite" ], "devDependencies": { "tap": "~0.3.2" }, "dependencies": { "mersenne": "~0.0.3", "statsd-parser": "~0.0.4" }, "licenses": [ { "type": "MIT", "url": "http://github.com/dscape/lynx/raw/master/LICENSE" } ] } lynx-0.2.1/tests/000077500000000000000000000000001236324704600136555ustar00rootroot00000000000000lynx-0.2.1/tests/counts-test.js000066400000000000000000000005231236324704600165030ustar00rootroot00000000000000var macros = require('./macros'); // // Our `counting` tests // Should match `tests/fixtures/counting.json` // macros.matchFixturesTest('counts', function runTest(connection) { connection.increment('foo.bar'); connection.decrement('foo.baz'); connection.decrement(['uno', 'two', 'trezentos']); connection.count('boaz', 101); }); lynx-0.2.1/tests/errors-test.js000066400000000000000000000017521236324704600165110ustar00rootroot00000000000000var macros = require('./macros') , lynx = macros.lynx , port = macros.udpServerPort , test = macros.test , fixture = require('./fixtures/errors.json') ; test('errors', function (t) { function on_error(actual) { var expected = fixture.shift(); // // Should return the function that invoked this and the arguments // for inspection // t.ok(actual.f, 'should have a reference to the function'); t.ok(actual.args, 'args should be supplied'); // // Message should match fixture // t.equal(expected, actual.message); // // No more tests to run // if(fixture.length === 0) { // // Nothing more to do. // t.end(); } } // // Wrong host // var connection = new lynx('locahost', port, {on_error: on_error}); connection.count(1); connection.count('foo', NaN); connection.count('foo', undefined); connection.send({'foo': '1|c'}, 0); connection.count('foo', 10); });lynx-0.2.1/tests/fixtures/000077500000000000000000000000001236324704600155265ustar00rootroot00000000000000lynx-0.2.1/tests/fixtures/counts.json000066400000000000000000000001301236324704600177260ustar00rootroot00000000000000[ "foo.bar:1|c" , "foo.baz:-1|c" , "uno:-1|c\ntwo:-1|c\ntrezentos:-1|c" , "boaz:101|c" ]lynx-0.2.1/tests/fixtures/errors.json000066400000000000000000000002611236324704600177340ustar00rootroot00000000000000[ "Can't set if its not even an array by now" , "Must be either a number or a string" , "Must be either a number or a string" , "Nothing to send" , "Failed sending the buffer" ]lynx-0.2.1/tests/fixtures/gauges.json000066400000000000000000000000541236324704600176730ustar00rootroot00000000000000[ "foo.gauge.1:500|g" , "foo.gauge.2:15|g" ]lynx-0.2.1/tests/fixtures/scopes.json000066400000000000000000000001641236324704600177160ustar00rootroot00000000000000[ "scope.bar:1|c" , "scope.baz:-1|c" , "scope.uno:-1|c\nscope.two:-1|c\nscope.trezentos:-1|c" , "scope.boaz:101|c" ]lynx-0.2.1/tests/fixtures/sets.json000066400000000000000000000000471236324704600174000ustar00rootroot00000000000000[ "set1.foo:765|s" , "set1.bar:567|s" ]lynx-0.2.1/tests/fixtures/stream-recv.json000066400000000000000000000003251236324704600206510ustar00rootroot00000000000000[ "foo.bar:1|c" , "foo.baz:-1|c" , "uno:-1|c" , "two:-1|c" , "trezentos:-1|c" , "boaz:101|c" , "foo.bar.time:500|ms" , "set1.bar:567|s" , "foo.gauge.2:15|g" , "boaz:101|c" , "set1.bar:567|s" , "foo.gauge.2:15|g" ]lynx-0.2.1/tests/fixtures/stream-send.json000066400000000000000000000003321236324704600206410ustar00rootroot00000000000000[ "foo.bar:1|c" , "foo.baz:-1|c" , "uno:-1|c\n two:-1|c\n trezentos:-1|c" , "boaz:101|c" , "foo.bar.time:500|ms\n set1.bar:567|s \n foo.gauge.2:15|g" , "boaz:1" , "01" , "|c\n set" , "1.bar:567|s \n foo.gauge.2:15|g" ]lynx-0.2.1/tests/fixtures/timings.json000066400000000000000000000001451236324704600200730ustar00rootroot00000000000000[ "foo.baz.time:10|ms" , "foo.bar.time:500|ms" , "foo.interval:~200|ms" , "bar.comes.first:~100|ms" ]lynx-0.2.1/tests/gauges-test.js000066400000000000000000000004011236324704600164360ustar00rootroot00000000000000var macros = require('./macros'); // // Our `gauges` tests // Should match `tests/fixtures/gauges.json` // macros.matchFixturesTest('gauges', function runTest(connection) { connection.gauge('foo.gauge.1', 500); connection.gauge('foo.gauge.2', 15); });lynx-0.2.1/tests/global_leaks.js000066400000000000000000000003441236324704600166330ustar00rootroot00000000000000var specify = require('specify') , lynx = require('../lib/lynx') ; specify('errors', function (assert) { var connection = new lynx('locahost', 86875); connection.increment('a'); assert.ok(true); }); specify.run();lynx-0.2.1/tests/macros.js000066400000000000000000000163511236324704600155050ustar00rootroot00000000000000var path = require('path') , dgram = require('dgram') , test = require('tap').test , lynx = require('../lib/lynx') , macros = exports ; // // Percentage allowed for errors in aproximations // Like, duration is around 100ms. Means for 10% error can be 10ms. // var MAX_APROX_ERROR = process.env.MAX_APROX_ERROR ? parseInt(MAX_APROX_ERROR, 10) : 10 ; // // Set the server port // macros.udpServerPort = 9753; // // Create a connection // macros.connection = new lynx('localhost', macros.udpServerPort); // // ### function udpServer(testName, onTest) // #### @onMessage {Function} Function to be run on each message // // Start a `udp` server. // macros.udpServer = function udpServer(onMessage) { var socket = dgram.createSocket('udp4', onMessage); // // Listen in some (not so) random port // socket.bind(macros.udpServerPort, 'localhost'); return socket; }; // // ### function udpFixturesServer(testName, onTest) // #### @testName {String} The test that is calling this, so we can load // the respective fixture // #### @onTest {Function} Function that returns the result of a specific // test // // Start a `udp` server that will expect an event that is // mocked in `fixtures` // macros.udpFixturesServer = function udpServer(testName, t, onTest) { // // Set the path for the fixture we want to load // var fixturePath = path.join('fixtures', testName + '.json'); // // Try to load the fixture. // This will break your program if you delete by mistake // var fixture = require('./' + fixturePath); // // The number of requests we expect to get // var nrRequests = fixture.length , iRequests = 0 ; // // Create a UDP Socket // var socket = macros.udpServer(function (message, remote) { // // We got another one // iRequests++; // // `remote.address` for remote address // `remote.port` for remote port // `remote.size` for data lenght // `message.toString('ascii', 0, remote.size)` for textual contents // var actual = macros.parseMessage(message, remote.size) , iExpected = fixture.indexOf(actual) ; // // Let's check if its an aproximation // if(!~iExpected) { // // In aproximations we note them as `foo:~10|s` // Lets see if we have any with the right key that is an aproximation // var aprox_fixtures_with_right_stat = fixture.filter(function (expctd) { var stat = expctd.split(':')[0] // expected stat key , aStat = actual.split(':')[0] // actual stat key , isAprox = ~expctd.indexOf('~') // is expected an aproximation? ; return stat === aStat && isAprox; }); var aprox_actual = aprox_fixtures_with_right_stat[0]; iExpected = fixture.indexOf(aprox_actual); } // // Found it // if (~iExpected) { var expected = fixture[iExpected]; // // Remove the found item from fixture to test // fixture.splice(iExpected, 1); // // Return our test results // onTest(true, {expected: expected, actual: actual, remaining: fixture}); } // // We didn't find that response in the response array // else { onTest(false, { expected: null, actual: actual, remaining: fixture}); } // // If we are done // if(iRequests === nrRequests) { // // Close the server // socket.close(); // // Tests are complete // t.end(); } }); }; // // ### function matchFixturesTest(testName, onTest) // #### @resource {String} The resource we are testing (gauges, sets, counts) // #### @f {Function} The actual udp client calls to be received by // our mock server // // 1. Loads fixtures for this resource and checks how many client requests // are going to exist // 2. Runs a tests that: // 2.1. Start a `udp` server that will expect a event that // is mocked in `fixtures` // 2.2. Runs client code that should match what has been mocked // macros.matchFixturesTest = function genericTest(resource, f) { var currentFixture = require('./fixtures/' + resource); // // All of our counting tests // test(resource + ' test', function (t) { // // Setup our server // macros.udpFixturesServer(resource, t, function onEachRequest(err, info) { // // Aproximation // if(info.expected && ~info.expected.indexOf('~')) { // // foobar : ~ 10 |ms // /(.*)? : ~ (.*)? \|ms/ // // ? means non eager, like don't eat multiple `:`. // var matchE = /(.*)?:~(.*)?\|ms/.exec(info.expected) // // Actual doesnt have `~` // , matchA = /(.*)?:(.*)?\|ms/.exec(info.actual) ; // // If we were able to extract values from both // if(matchE && typeof matchE[2] === 'string' && matchA && typeof matchA[2] === 'string') { // // Get our aproximate number // var aproximation = parseInt(matchE[2], 10) , valueA = parseInt(matchA[2], 10) ; // // Our upper bound // var ubound = aproximation + (aproximation * MAX_APROX_ERROR / 100); t.ok(ubound >= valueA, 'value deviated from ' + aproximation + ' by more than +' + MAX_APROX_ERROR + '%. [' + valueA + ']'); // // Our lower bound // var lbound = aproximation - (aproximation * MAX_APROX_ERROR / 100); t.ok(lbound <= valueA, 'value deviated from ' + aproximation + ' by more than -' + MAX_APROX_ERROR + '%. [' + valueA + ']'); } else { // // Show the expected vs actual string // t.equal(info.expected, info.actual, 'Equality check for ' + info.actual); } } // // On each response check if they are identical // else { // // Just treat it like any other thing. // but hey, that fixture is wrong dude! // if(typeof info.expected === 'string') { t.equal(info.expected, info.actual, 'Equality check for ' + info.actual); } // // This failed, let's show the array of possibilities that could // have matched // else { t.equal(info.remaining, [info.actual], "Didn't find value " + info.actual + ' in array of possible fixtures'); } } }); // // Run our client code // if(resource === 'scopes') { macros.connection.close(); macros.connection = new lynx('localhost', macros.udpServerPort, { scope: 'scope' }); } f(macros.connection); }); }; // // ### function parseMessage(testName, onTest) // #### @message {String} Message to decode // // Start a `udp` server. // macros.parseMessage = function parseMessage(message, size) { return message.toString('ascii', 0, size); }; // // Export simple `tap` tests // macros.test = test; // // Export `lynx` // macros.lynx = lynx; // // Export MAX_APROX_ERROR // macros.MAX_APROX_ERROR = MAX_APROX_ERROR;lynx-0.2.1/tests/sampling-test.js000066400000000000000000000052351236324704600170070ustar00rootroot00000000000000var macros = require('./macros') , statsd = require('statsd-parser') , lynx = macros.lynx , test = macros.test , udpServer = macros.udpServer , connection = macros.connection , count = 0 , finished = false ; // // TOTAL is the number of iterations to do // DESIRED is the minimum number of requests expected // SAMPLE Number of samples to send, e.g. @0.1 (1 in 10) // var DESIRED = 90 , TOTAL = 1000 , SAMPLE = 0.1 ; // // Try to do this a thousand times // [1,2,3,...,1000] // var coll = []; for(i=0; i DESIRED) { finished = true; t.ok(true, 'Reached ' + DESIRED + ' on ' + (TOTAL - coll.length) + ' packets.'); server.close(); } }); // // Run all the iterations // var runAll = function(coll, callback) { (function iterate() { if (coll.length === 0) { return callback(); } coll.pop(); setTimeout(function send_packet() { // // Send a sample // connection.gauge('spl.foo', 500, SAMPLE); process.nextTick(iterate); }, Math.ceil(Math.random() * 10)); })(); }; runAll(coll, function() { if (finished) { t.ok(true, 'Reached ' + DESIRED + ' on ' + TOTAL + ' packets.'); t.end(); return; } // // If we reached the end and this has not closed by having // the desired amount of requests // t.ok(false, 'Didnt reach the desired amount of packets ' + DESIRED + '/' + TOTAL + ' was -> ' + count); server.close(); t.end(); }); }); // // TODO: Sampling with irregular batches //lynx-0.2.1/tests/scopes-test.js000066400000000000000000000005131236324704600164630ustar00rootroot00000000000000var macros = require('./macros'); // // Our `counting` tests // Should match `tests/fixtures/counting.json` // macros.matchFixturesTest('scopes', function runTest(connection) { connection.increment('bar'); connection.decrement('baz'); connection.decrement(['uno', 'two', 'trezentos']); connection.count('boaz', 101); }); lynx-0.2.1/tests/sets-test.js000066400000000000000000000003621236324704600161470ustar00rootroot00000000000000var macros = require('./macros'); // // Our `sets` tests // Should match `tests/fixtures/sets.json` // macros.matchFixturesTest('sets', function runTest(connection) { connection.set('set1.foo', 765); connection.set('set1.bar', 567); });lynx-0.2.1/tests/stream-test.js000066400000000000000000000052031236324704600164630ustar00rootroot00000000000000var Stream = require('stream') , macros = require('./macros') , lynx = macros.lynx , port = macros.udpServerPort , udpServer = macros.udpServer , test = macros.test , fixture_send = require('./fixtures/stream-send.json') , fixture_recv = require('./fixtures/stream-recv.json') ; var server = udpServer(function () {}); // // ### function createDelayedStream() // // Creates a stream that reads stuff in fixtures and emits each line every // 10ms or so // function createDelayedStream() { // // Make a new plain vanilla readable stream // var delayed_stream = new Stream(); delayed_stream.readable = true; // // Set an interval to emit every 10ms or so // var interval = setInterval(function () { var current = fixture_send.shift(); if(current) { // // Emit the current stream // delayed_stream.emit('data', current); } else { // // We have nothing else to emit, so close this thing // delayed_stream.emit('end'); clearInterval(interval); } }, 10); return delayed_stream; } // // ### function createTestStream(t) // #### @t {Object} A tap test assertion // // Tests if the things being passed to this stream match expectations // function createTestStream(t) { // // Make a new plain vanilla writable stream // var test_stream = new Stream(); test_stream.writable = true; // // Handle `Stream.prototype.write` // test_stream.write = function (buf) { var expected = fixture_recv.shift(); t.equal(expected, buf.toString(), ' should be equal to ' + expected); if(fixture_recv.length === 0) { test_stream.end(); } }; // // Handle `Stream.prototype.end` // And we should end our tests here // // test_stream.end = function end(buf) { if (arguments.length) { test_stream.write(buf); } // // Close our server socket // server.close(); // // End our little experiment // test_stream.writable = false; t.end(); }; // // Handle `Stream.prototype.destroy` // test_stream.destroy = function () { test_stream.writable = false; }; return test_stream; } test('streams', function (t) { // // ### function on_error(err) // #### @err {Error} Error object // // Assertion to run if we get any errors // function on_error(err) { t.equal({}, err, "didn't expect any errors"); // // End early // t.end(); } // // Our connection // var conn = new lynx('localhost', port, {on_error: on_error}); createDelayedStream() .pipe(conn) .pipe(createTestStream(t)) ; });lynx-0.2.1/tests/timings-test.js000066400000000000000000000025561236324704600166520ustar00rootroot00000000000000var macros = require('./macros'); // // Our `timing` fixture tests // Should match `tests/fixtures/timing.json` // macros.matchFixturesTest('timings', function runTest(connection) { // // Basic Tests // connection.timing('foo.baz.time', 10); connection.timing('foo.bar.time', 500); // // Constructing a timer object // var timer = connection.createTimer('foo.interval'); // // Wait 200ms // setTimeout(function () { // // Stop the timer // timer.stop(); }, 200); // // A second timer // // Wait 100ms // var second_timer = connection.createTimer('bar.comes.first'); setTimeout(function () { // // Stop the timer // second_timer.stop(); }, 100); // // Attempts to stop the timer again but before `foo.interval` // If someone breaks the `only stop once code` this will cause an error // because it will emit before the `foo.interval` and it wont be equal // // Wait 150ms // setTimeout(function () { // // Atempt to stop already stopped timer // Will console.log `Can't stop a timer twice` // // We are not testing for this error, cause its just an error message // but this would be raised on scenarios where a more strict error handler // is enforced by the user // connection.on('error', function (err) { }); second_timer.stop(); }, 150); });