Comparing version 0.0.1 to 0.0.3
@@ -16,5 +16,5 @@ # Contributing | ||
Tests are written in `node-tap`. If you want to create a new test create a file named `myTestName-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 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 `fooBar` this helper function will read a fixtures located in `tests/fixtures/fooBar.json`. | ||
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`. | ||
@@ -21,0 +21,0 @@ Each fixture is an array containing strings, the strings that we expect the client to send when we issue certain commands. |
562
lib/lynx.js
@@ -8,21 +8,88 @@ var dgram = require('dgram') | ||
, mt = new mersenne.MersenneTwister19937() | ||
, noop = function () {} | ||
; | ||
// | ||
// Max idle time for a ephemeral socket | ||
// | ||
var EPHEMERAL_LIFETIME_MS = 1000; | ||
function Client(host, port, socket) { | ||
this.host = host; | ||
this.port = port; | ||
// | ||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ constructors ~~ | ||
// | ||
// optional shared socket | ||
this.socket = socket; | ||
// | ||
// ### 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.onError {Function} A function to execute on errors | ||
// | ||
// var client = new lynx('localhost', 8125); | ||
// | ||
// Returns a new `Lynx` client | ||
// | ||
function Lynx(host, port, options) { | ||
// | ||
// Server hostname and port | ||
// | ||
this.host = host; | ||
this.port = port; | ||
// 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; | ||
// | ||
// Optional shared socket | ||
// | ||
this.socket = options && options.socket; | ||
// | ||
// 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; | ||
// | ||
// ### 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 | ||
// | ||
function default_error_handler (err) { | ||
console.log(err.message); | ||
} | ||
// | ||
// Set out error handling code | ||
// | ||
this.onError = options && typeof options.onError === "function" | ||
? options.onError | ||
: default_error_handler | ||
; | ||
} | ||
Client.prototype.Timer = function (stat, sample_rate) { | ||
// | ||
// ### 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.Timer = function (stat, sample_rate) { | ||
var self = this | ||
@@ -35,3 +102,4 @@ , start_time = new Date ().getTime() | ||
// | ||
// Stops the timer and issues the respective interval | ||
// Stops the timer and issues the respective interval. | ||
// Check example above | ||
// | ||
@@ -43,2 +111,5 @@ function stop() { | ||
// | ||
// The closure that is returned | ||
// | ||
return { | ||
@@ -52,124 +123,397 @@ stat : stat | ||
Client.prototype.timing = function (stat, duration, sample_rate) { | ||
var self = this; | ||
var stats = {}; | ||
stats[stat] = duration+"|ms"; | ||
self.send(stats, sample_rate); | ||
// | ||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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 (stats, sample_rate) { | ||
this.count(stats, 1, sample_rate); | ||
}; | ||
Client.prototype.set = function (stat, value, sample_rate) { | ||
var self = this; | ||
var stats = {}; | ||
stats[stat] = value+"|s"; | ||
self.send(stats, 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 (stats, sample_rate) { | ||
this.count(stats, -1, sample_rate); | ||
}; | ||
Client.prototype.gauge = function (stat, value, sample_rate) { | ||
var self = this; | ||
var stats = {}; | ||
stats[stat] = value+"|g"; | ||
self.send(stats, 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 (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.onError( | ||
{ "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.onError( | ||
{ "message" : "Must be either a number or a string" | ||
, "f" : "count" | ||
, "args" : arguments | ||
}); | ||
return; | ||
} | ||
// | ||
// Batch up all these stats to send | ||
// | ||
batch = {}; | ||
for(var i in stats) { | ||
batch[stats[i]] = delta + "|c"; | ||
} | ||
// | ||
// Send all these stats | ||
// | ||
this.send(batch, sample_rate); | ||
}; | ||
Client.prototype.increment = function (stats, sample_rate) { | ||
var self = this; | ||
self.update_stats(stats, 1, 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 (stat, duration, sample_rate) { | ||
var stats = {}; | ||
stats[stat] = duration + "|ms"; | ||
this.send(stats, sample_rate); | ||
}; | ||
Client.prototype.decrement = function (stats, sample_rate) { | ||
var self = this; | ||
self.update_stats(stats, -1, 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 (stat, value, sample_rate) { | ||
var stats = {}; | ||
stats[stat] = value + "|s"; | ||
this.send(stats, sample_rate); | ||
}; | ||
Client.prototype.update_stats = function (stats, delta, sampleRate) { | ||
var self = this; | ||
if (typeof(stats) === 'string') { | ||
stats = [stats]; | ||
} | ||
if (!delta) { | ||
delta=1; | ||
} | ||
data = {}; | ||
for (var i=0; i<stats.length; i++){ | ||
data[stats[i]] = delta+"|c"; | ||
} | ||
self.send(data, sampleRate); | ||
// | ||
// ### 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 (stat, value, sample_rate) { | ||
var stats = {}; | ||
stats[stat] = value + "|g"; | ||
this.send(stats, sample_rate); | ||
}; | ||
// 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. | ||
Client.prototype._update_last_used = function () { | ||
if (this.ephemeral_socket) { | ||
if (this.last_used_timer) clearTimeout(this.last_used_timer); | ||
var self = this; | ||
this.last_used_timer = setTimeout(function() { | ||
if (self.ephemeral_socket) self.ephemeral_socket.close(); | ||
delete self.ephemeral_socket; | ||
}, EPHEMERAL_LIFETIME_MS); | ||
// | ||
// ### 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 (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.onError( | ||
{ "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 (stat) { | ||
return 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'); | ||
} | ||
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 | ||
// | ||
socket.send(buffer, 0, buffer.length, this.port, this.host, noop); | ||
// | ||
// Error: Failed sending the buffer | ||
// | ||
socket.on('error', function (err) { | ||
err.reason = err.message; | ||
err.f = "send"; | ||
err.message = "Failed sending the buffer"; | ||
err.args = arguments; | ||
self.onError(err); | ||
return; | ||
}); | ||
}; | ||
Client.prototype.send_data = function (buffer) { | ||
var self = this; | ||
var socket; | ||
// | ||
// ### 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 () { | ||
// | ||
// User defined socket | ||
// | ||
if (this.socket) { | ||
this.socket.close(); | ||
this.socket = undefined; | ||
} | ||
if (this.socket === undefined) { | ||
if (!this.ephemeral_socket) { | ||
this.ephemeral_socket = dgram.createSocket('udp4'); | ||
} | ||
socket = this.ephemeral_socket; | ||
} else { | ||
socket = this.socket; | ||
} | ||
// | ||
// Ephemeral socket | ||
// | ||
if (this.ephemeral_socket) { | ||
this.ephemeral_socket.close(); | ||
this.ephemeral_socket = undefined; | ||
} | ||
this._update_last_used(); | ||
// | ||
// Timer | ||
// | ||
if (this.last_used_timer) { | ||
cancelTimeout(this.last_used_timer); | ||
this.last_used_timer = undefined; | ||
} | ||
}; | ||
socket.send(buffer, 0, buffer.length, this.port, this.host, function (err, bytes) { | ||
if (err) { | ||
console.log("Error while sending data:", err.msg); | ||
} | ||
// | ||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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 (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 (stat) { | ||
value = stats[stat]; | ||
sampled_stats[stat] = value + "|@" + sample_rate; | ||
}); | ||
} | ||
return sampled_stats; | ||
}; | ||
Client.prototype.send = function (data, sample_rate) { | ||
var self = this; | ||
if (!sample_rate) { | ||
sample_rate = 1; | ||
} | ||
// | ||
// ### 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 () { | ||
var self = this; | ||
var sampled_data = {}; | ||
if(sample_rate < 1) { | ||
if (mt.genrand_real2(0,1) <= sample_rate) { | ||
for (var stat in data) { | ||
value = data[stat]; | ||
sampled_data[stat] = value + "|@" + sample_rate; | ||
} | ||
} | ||
// | ||
// Only update on the ephemeral socket | ||
// | ||
if (this.ephemeral_socket) { | ||
// | ||
// Clear existing timeouts | ||
// | ||
if (this.last_used_timer) { | ||
clearTimeout(this.last_used_timer); | ||
} | ||
else { | ||
sampled_data=data; | ||
} | ||
for (var stat in sampled_data) { | ||
var send_data = stat+":"+sampled_data[stat]; | ||
this.send_data(new Buffer(send_data)); | ||
} | ||
}; | ||
Client.prototype.close = function () { | ||
if (this.socket) { | ||
this.socket.close(); | ||
this.socket = undefined; | ||
} | ||
if (this.ephemeral_socket) { | ||
this.ephemeral_socket.close(); | ||
this.ephemeral_socket = undefined; | ||
} | ||
if (this.last_used_timer) { | ||
cancelTimeout(this.last_used_timer); | ||
this.last_used_timer = undefined; | ||
} | ||
// | ||
// 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); | ||
} | ||
}; | ||
module.exports = Client; | ||
// | ||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ exports ~~ | ||
// | ||
module.exports = Lynx; |
{ "name" : "lynx" | ||
, "description" : "Minimalistic StatsD client for Node.js programs" | ||
, "version" : "0.0.1" | ||
, "version" : "0.0.3" | ||
, "author" : "Lloyd Hilaiel" | ||
@@ -5,0 +5,0 @@ , "contributors": [ "Nuno Job <nunojobpinto@gmail.com> (http://nunojob.com)" ] |
# lynx | ||
A minimalistic node.js client for [StatsD] server. Fork of original work by [sivy] | ||
A minimalistic node.js client for [statsd] server. Fork of original work by [sivy] | ||
@@ -9,4 +9,4 @@ `lynx` features: | ||
statsd | ||
* **Re-usable UDP Connections** – Keeps UDP connections open for a certain time | ||
* **Errors** - Pluggable error handling, by default errors are ignored | ||
* **Re-usable UDP Connections** — Keeps UDP connections open for a certain time | ||
* **Errors** — Pluggable error handling, by default errors are ignored | ||
@@ -24,5 +24,7 @@ ## Quick Start | ||
> 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 | ||
This is the equivalent to: | ||
@@ -33,6 +35,76 @@ ``` sh | ||
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 | ||
``` | ||
## tests | ||
The protocol is super simple, so feel free to check out the source code to understand how everything works. | ||
## Advanced | ||
### Timers | ||
If you wish to measure timing you can use the `timer()` functionality. | ||
``` js | ||
var metrics = new lynx('localhost', 8125) | ||
, timer = metrics.Timer('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 `onError` function when instantiating the `lynx` client. | ||
``` js | ||
function onError(err) { | ||
console.log(err.message); | ||
} | ||
var connection = new lynx('localhost', 1234, {onError: onError}); | ||
``` | ||
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`. | ||
@@ -44,3 +116,3 @@ | ||
## meta | ||
## Meta | ||
@@ -55,2 +127,3 @@ `\. ,/' | ||
* 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` | ||
@@ -64,2 +137,2 @@ * home: <http://github.com/dscape/lynx> | ||
[sivy]: https://github.com/sivy/node-statsd | ||
[StatsD]: https://github.com/etsy/statsd | ||
[statsd]: https://github.com/etsy/statsd |
@@ -8,4 +8,6 @@ var macros = require('./macros'); | ||
macros.matchFixturesTest('counts', function runTest(connection) { | ||
connection.increment("foo.bar"); | ||
connection.decrement("foo.baz"); | ||
connection.increment('foo.bar'); | ||
connection.decrement('foo.baz'); | ||
connection.decrement(['uno', 'two', 'trezentos']); | ||
connection.count('boaz', 101); | ||
}); |
[ "foo.bar:1|c" | ||
, "foo.baz:-1|c" | ||
, "uno:-1|c\ntwo:-1|c\ntrezentos:-1|c" | ||
, "boaz:101|c" | ||
] |
@@ -8,4 +8,4 @@ var macros = require('./macros'); | ||
macros.matchFixturesTest('gauges', function runTest(connection) { | ||
connection.gauge("foo.gauge.1", 500); | ||
connection.gauge("foo.gauge.2", 15); | ||
connection.gauge('foo.gauge.1', 500); | ||
connection.gauge('foo.gauge.2', 15); | ||
}); |
@@ -34,3 +34,3 @@ var path = require('path') | ||
macros.updServer = function udpServer(onMessage) { | ||
var socket = dgram.createSocket("udp4", onMessage); | ||
var socket = dgram.createSocket('udp4', onMessage); | ||
@@ -52,10 +52,10 @@ // | ||
// | ||
// Start a `udp` server that will expect a certain order of events that is | ||
// Start a `udp` server that will expect an event that is | ||
// mocked in `fixtures` | ||
// | ||
macros.udpFixturesServer = function udpServer(testName, onTest) { | ||
macros.udpFixturesServer = function udpServer(testName, t, onTest) { | ||
// | ||
// Set the path for the fixture we want to load | ||
// | ||
var fixturePath = path.join("fixtures", testName + ".json"); | ||
var fixturePath = path.join('fixtures', testName + '.json'); | ||
@@ -66,3 +66,3 @@ // | ||
// | ||
var fixture = require("./" + fixturePath); | ||
var fixture = require('./' + fixturePath); | ||
@@ -86,7 +86,2 @@ // | ||
// | ||
// We expect the first item in our fixture | ||
// | ||
var expected = fixture.shift(); | ||
// | ||
// `remote.address` for remote address | ||
@@ -97,14 +92,63 @@ // `remote.port` for remote port | ||
// | ||
var actual = macros.parseMessage(message, remote.size); | ||
var actual = macros.parseMessage(message, remote.size) | ||
, iExpected = fixture.indexOf(actual) | ||
; | ||
// | ||
// Return our test results | ||
// Let's check if its an aproximation | ||
// | ||
onTest(actual === expected, { expected: expected, actual: actual }); | ||
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); | ||
} | ||
// | ||
// If we are done close the server | ||
// 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(); | ||
} | ||
@@ -123,3 +167,3 @@ }); | ||
// 2. Runs a tests that: | ||
// 2.1. Start a `udp` server that will expect a certain order of events that | ||
// 2.1. Start a `udp` server that will expect a event that | ||
// is mocked in `fixtures` | ||
@@ -129,30 +173,12 @@ // 2.2. Runs client code that should match what has been mocked | ||
macros.matchFixturesTest = function genericTest(resource, f) { | ||
var currentFixture = require('./fixtures/' + resource) | ||
, nrTests = currentFixture.length | ||
; | ||
var currentFixture = require('./fixtures/' + resource); | ||
// | ||
// Correct `nrTests`. Each aproximation test (contains `~`) does two | ||
// assertions | ||
// | ||
currentFixture.forEach(function (value) { | ||
if(value.indexOf("~") !== -1) { | ||
nrTests++; | ||
} | ||
}); | ||
// | ||
// All of our counting tests | ||
// | ||
test(resource + " test", function (t) { | ||
test(resource + ' test', function (t) { | ||
// | ||
// Plan for as many tests as we have fixtures | ||
// Double for `~` (aproximation) tests. | ||
// | ||
t.plan(nrTests); | ||
// | ||
// Setup our server | ||
// | ||
macros.udpFixturesServer(resource, function onEachRequest(err, info) { | ||
macros.udpFixturesServer(resource, t, function onEachRequest(err, info) { | ||
@@ -162,3 +188,3 @@ // | ||
// | ||
if(info.expected.indexOf("~") !== -1) { | ||
if(info.expected && ~info.expected.indexOf('~')) { | ||
// | ||
@@ -180,4 +206,4 @@ // foobar : ~ 10 |ms | ||
// | ||
if(matchE && typeof matchE[2] === "string" && | ||
matchA && typeof matchA[2] === "string") { | ||
if(matchE && typeof matchE[2] === 'string' && | ||
matchA && typeof matchA[2] === 'string') { | ||
// | ||
@@ -195,4 +221,4 @@ // Get our aproximate number | ||
t.ok(ubound >= valueA, "value deviated from " + aproximation + | ||
" by more than +" + MAX_APROX_ERROR + "%. [" + valueA + "]"); | ||
t.ok(ubound >= valueA, 'value deviated from ' + aproximation + | ||
' by more than +' + MAX_APROX_ERROR + '%. [' + valueA + ']'); | ||
@@ -204,11 +230,11 @@ // | ||
t.ok(lbound <= valueA, "value deviated from " + aproximation + | ||
" by more than -" + MAX_APROX_ERROR + "%. [" + valueA + "]"); | ||
t.ok(lbound <= valueA, 'value deviated from ' + aproximation + | ||
' by more than -' + MAX_APROX_ERROR + '%. [' + valueA + ']'); | ||
} | ||
else { | ||
// | ||
// Just treat it like any other thing. | ||
// but hey, that fixture is wrong dude! | ||
// Show the expected vs actual string | ||
// | ||
t.equal(info.expected, info.actual); | ||
t.equal(info.expected, info.actual, | ||
'Equality check for ' + info.actual); | ||
} | ||
@@ -220,3 +246,19 @@ } | ||
else { | ||
t.equal(info.expected, info.actual); | ||
// | ||
// 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'); | ||
} | ||
} | ||
@@ -223,0 +265,0 @@ }); |
var macros = require('./macros') | ||
, lynx = macros.lynx | ||
, test = macros.test | ||
@@ -29,7 +30,7 @@ , updServer = macros.updServer | ||
// | ||
// We are going to do one thousand packets | ||
// We are going to do one thousand `TOTAL` packets | ||
// and see if we hit our minimums | ||
// | ||
// When you specify sampling `lynx` must track that it only send the amount | ||
// of packages you are specifying (e.g. 1 in each 10 for @0.1) | ||
// of packages you are specifying (e.g. 1 in each 10 for @0.1 as in `SAMPLE`) | ||
// | ||
@@ -45,18 +46,12 @@ // To do this we use random numbers, making our process not perfect but | ||
var server = updServer(function (message, remote) { | ||
// | ||
// do nothing if finished | ||
// | ||
if(finished) { | ||
return; | ||
} | ||
count++; | ||
// | ||
// We have hit our minimums | ||
// When we finally hit our lower threshold | ||
// | ||
if(count > DESIRED) { | ||
finished = true; | ||
t.ok(true, "Reached " + DESIRED + " on " + (TOTAL - coll.length) + " packets."); | ||
t.end(); | ||
t.ok(true, "Reached " + DESIRED + " on " + (TOTAL - coll.length) + | ||
" packets."); | ||
server.close(); | ||
} | ||
@@ -85,2 +80,7 @@ }); | ||
runAll(coll, function() { | ||
if (finished) { | ||
t.ok(true, "Reached " + DESIRED + " on " + TOTAL + " packets."); | ||
t.end(); | ||
return; | ||
} | ||
// | ||
@@ -95,2 +95,6 @@ // If we reached the end and this has not closed by having | ||
}); | ||
}); | ||
}); | ||
// | ||
// TODO: Sampling with irregular batches | ||
// |
@@ -8,4 +8,4 @@ var macros = require('./macros'); | ||
macros.matchFixturesTest('sets', function runTest(connection) { | ||
connection.set("set1.foo", 765); | ||
connection.set("set1.bar", 567); | ||
connection.set('set1.foo', 765); | ||
connection.set('set1.bar', 567); | ||
}); |
@@ -17,3 +17,3 @@ var macros = require('./macros'); | ||
// | ||
var timer = connection.Timer("foo.interval"); | ||
var timer = connection.Timer('foo.interval'); | ||
@@ -20,0 +20,0 @@ // |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
32455
19
916
133
2