├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── lib └── statsd.js ├── package.json └── test └── test_statsd.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea/ 3 | *.iml 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "0.6" 5 | - "0.8" 6 | - "0.10" 7 | 8 | install: 9 | - npm install 10 | 11 | script: 12 | - npm test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2011 Steve Ivy. All rights reserved. 2 | Permission is hereby granted, free of charge, to any person obtaining a copy 3 | of this software and associated documentation files (the "Software"), to 4 | deal in the Software without restriction, including without limitation the 5 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 6 | sell copies of the Software, and to permit persons to whom the Software is 7 | furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in 10 | all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 17 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 18 | IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-statsd 2 | 3 | A node.js client for [Etsy](http://etsy.com)'s [StatsD](https://github.com/etsy/statsd) server. 4 | 5 | This client will let you fire stats at your StatsD server from a node.js application. 6 | 7 | node-statsd Runs and is tested on Node 0.6+ on all *nix platforms and 0.8+ on all platforms including Windows. 8 | 9 | [![Build Status](https://secure.travis-ci.org/sivy/node-statsd.png?branch=master)](http://travis-ci.org/sivy/node-statsd) 10 | 11 | ## Installation 12 | 13 | ``` 14 | $ npm install node-statsd 15 | ``` 16 | 17 | ## Usage 18 | 19 | All initialization parameters are optional. 20 | 21 | Parameters (specified as an options hash): 22 | * `host`: The host to send stats to `default: localhost` 23 | * `port`: The port to send stats to `default: 8125` 24 | * `prefix`: What to prefix each stat name with `default: ''` 25 | * `suffix`: What to suffix each stat name with `default: ''` 26 | * `globalize`: Expose this StatsD instance globally? `default: false` 27 | * `cacheDns`: Cache the initial dns lookup to *host* `default: false` 28 | * `mock`: Create a mock StatsD instance, sending no stats to the server? `default: false` 29 | * `global_tags`: Optional tags that will be added to every metric `default: []` 30 | 31 | All StatsD methods have the same API: 32 | * `name`: Stat name `required` 33 | * `value`: Stat value `required except in increment/decrement where it defaults to 1/-1 respectively` 34 | * `sampleRate`: Sends only a sample of data to StatsD `default: 1` 35 | * `tags`: The Array of tags to add to metrics `default: []` 36 | * `callback`: The callback to execute once the metric has been sent 37 | 38 | If an array is specified as the `name` parameter each item in that array will be sent along with the specified value. 39 | 40 | ```javascript 41 | var StatsD = require('node-statsd'), 42 | client = new StatsD(); 43 | 44 | // Timing: sends a timing command with the specified milliseconds 45 | client.timing('response_time', 42); 46 | 47 | // Increment: Increments a stat by a value (default is 1) 48 | client.increment('my_counter'); 49 | 50 | // Decrement: Decrements a stat by a value (default is -1) 51 | client.decrement('my_counter'); 52 | 53 | // Histogram: send data for histogram stat 54 | client.histogram('my_histogram', 42); 55 | 56 | // Gauge: Gauge a stat by a specified amount 57 | client.gauge('my_gauge', 123.45); 58 | 59 | // Set: Counts unique occurrences of a stat (alias of unique) 60 | client.set('my_unique', 'foobar'); 61 | client.unique('my_unique', 'foobarbaz'); 62 | 63 | // Incrementing multiple items 64 | client.increment(['these', 'are', 'different', 'stats']); 65 | 66 | // Sampling, this will sample 25% of the time the StatsD Daemon will compensate for sampling 67 | client.increment('my_counter', 1, 0.25); 68 | 69 | // Tags, this will add user-defined tags to the data 70 | client.histogram('my_histogram', 42, ['foo', 'bar']); 71 | 72 | // Using the callback 73 | client.set(['foo', 'bar'], 42, function(error, bytes){ 74 | //this only gets called once after all messages have been sent 75 | if(error){ 76 | console.error('Oh noes! There was an error:', error); 77 | } else { 78 | console.log('Successfully sent', bytes, 'bytes'); 79 | } 80 | }); 81 | 82 | // Sampling, tags and callback are optional and could be used in any combination 83 | client.histogram('my_histogram', 42, 0.25); // 25% Sample Rate 84 | client.histogram('my_histogram', 42, ['tag']); // User-defined tag 85 | client.histogram('my_histogram', 42, next); // Callback 86 | client.histogram('my_histogram', 42, 0.25, ['tag']); 87 | client.histogram('my_histogram', 42, 0.25, next); 88 | client.histogram('my_histogram', 42, ['tag'], next); 89 | client.histogram('my_histogram', 42, 0.25, ['tag'], next); 90 | ``` 91 | 92 | ## Errors 93 | 94 | In the event that there is a socket error, `node-statsd` will allow this error to bubble up. If you would like to catch the errors, just attach a listener to the socket property on the instance. 95 | 96 | ```javascript 97 | client.socket.on('error', function(error) { 98 | return console.error("Error in socket: ", error); 99 | }); 100 | ``` 101 | 102 | If you want to catch errors in sending a message then use the callback provided. 103 | 104 | ## License 105 | 106 | node-statsd is licensed under the MIT license. 107 | 108 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/statsd'); -------------------------------------------------------------------------------- /lib/statsd.js: -------------------------------------------------------------------------------- 1 | var dgram = require('dgram'), 2 | dns = require('dns'); 3 | 4 | /** 5 | * The UDP Client for StatsD 6 | * @param options 7 | * @option host {String} The host to connect to default: localhost 8 | * @option port {String|Integer} The port to connect to default: 8125 9 | * @option prefix {String} An optional prefix to assign to each stat name sent 10 | * @option suffix {String} An optional suffix to assign to each stat name sent 11 | * @option globalize {boolean} An optional boolean to add "statsd" as an object in the global namespace 12 | * @option cacheDns {boolean} An optional option to only lookup the hostname -> ip address once 13 | * @option mock {boolean} An optional boolean indicating this Client is a mock object, no stats are sent. 14 | * @option global_tags {Array=} Optional tags that will be added to every metric 15 | * @constructor 16 | */ 17 | var Client = function (host, port, prefix, suffix, globalize, cacheDns, mock, global_tags) { 18 | var options = host || {}, 19 | self = this; 20 | 21 | if(arguments.length > 1 || typeof(host) === 'string'){ 22 | options = { 23 | host : host, 24 | port : port, 25 | prefix : prefix, 26 | suffix : suffix, 27 | globalize : globalize, 28 | cacheDns : cacheDns, 29 | mock : mock === true, 30 | global_tags : global_tags 31 | }; 32 | } 33 | 34 | this.host = options.host || 'localhost'; 35 | this.port = options.port || 8125; 36 | this.prefix = options.prefix || ''; 37 | this.suffix = options.suffix || ''; 38 | this.socket = dgram.createSocket('udp4'); 39 | this.mock = options.mock; 40 | this.global_tags = options.global_tags || []; 41 | 42 | if(options.cacheDns === true){ 43 | dns.lookup(options.host, function(err, address, family){ 44 | if(err == null){ 45 | self.host = address; 46 | } 47 | }); 48 | } 49 | 50 | if(options.globalize){ 51 | global.statsd = this; 52 | } 53 | }; 54 | 55 | /** 56 | * Represents the timing stat 57 | * @param stat {String|Array} The stat(s) to send 58 | * @param time {Number} The time in milliseconds to send 59 | * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional. 60 | * @param tags {Array=} The Array of tags to add to metrics. Optional. 61 | * @param callback {Function=} Callback when message is done being delivered. Optional. 62 | */ 63 | Client.prototype.timing = function (stat, time, sampleRate, tags, callback) { 64 | this.sendAll(stat, time, 'ms', sampleRate, tags, callback); 65 | }; 66 | 67 | /** 68 | * Increments a stat by a specified amount 69 | * @param stat {String|Array} The stat(s) to send 70 | * @param value The value to send 71 | * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional. 72 | * @param tags {Array=} The Array of tags to add to metrics. Optional. 73 | * @param callback {Function=} Callback when message is done being delivered. Optional. 74 | */ 75 | Client.prototype.increment = function (stat, value, sampleRate, tags, callback) { 76 | this.sendAll(stat, value || 1, 'c', sampleRate, tags, callback); 77 | }; 78 | 79 | /** 80 | * Decrements a stat by a specified amount 81 | * @param stat {String|Array} The stat(s) to send 82 | * @param value The value to send 83 | * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional. 84 | * @param tags {Array=} The Array of tags to add to metrics. Optional. 85 | * @param callback {Function=} Callback when message is done being delivered. Optional. 86 | */ 87 | Client.prototype.decrement = function (stat, value, sampleRate, tags, callback) { 88 | this.sendAll(stat, -value || -1, 'c', sampleRate, tags, callback); 89 | }; 90 | 91 | /** 92 | * Represents the histogram stat 93 | * @param stat {String|Array} The stat(s) to send 94 | * @param value The value to send 95 | * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional. 96 | * @param tags {Array=} The Array of tags to add to metrics. Optional. 97 | * @param callback {Function=} Callback when message is done being delivered. Optional. 98 | */ 99 | Client.prototype.histogram = function (stat, value, sampleRate, tags, callback) { 100 | this.sendAll(stat, value, 'h', sampleRate, tags, callback); 101 | }; 102 | 103 | 104 | /** 105 | * Gauges a stat by a specified amount 106 | * @param stat {String|Array} The stat(s) to send 107 | * @param value The value to send 108 | * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional. 109 | * @param tags {Array=} The Array of tags to add to metrics. Optional. 110 | * @param callback {Function=} Callback when message is done being delivered. Optional. 111 | */ 112 | Client.prototype.gauge = function (stat, value, sampleRate, tags, callback) { 113 | this.sendAll(stat, value, 'g', sampleRate, tags, callback); 114 | }; 115 | 116 | /** 117 | * Counts unique values by a specified amount 118 | * @param stat {String|Array} The stat(s) to send 119 | * @param value The value to send 120 | * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional. 121 | * @param tags {Array=} The Array of tags to add to metrics. Optional. 122 | * @param callback {Function=} Callback when message is done being delivered. Optional. 123 | */ 124 | Client.prototype.unique = 125 | Client.prototype.set = function (stat, value, sampleRate, tags, callback) { 126 | this.sendAll(stat, value, 's', sampleRate, tags, callback); 127 | }; 128 | 129 | /** 130 | * Checks if stats is an array and sends all stats calling back once all have sent 131 | * @param stat {String|Array} The stat(s) to send 132 | * @param value The value to send 133 | * @param sampleRate {Number=} The Number of times to sample (0 to 1). Optional. 134 | * @param tags {Array=} The Array of tags to add to metrics. Optional. 135 | * @param callback {Function=} Callback when message is done being delivered. Optional. 136 | */ 137 | Client.prototype.sendAll = function(stat, value, type, sampleRate, tags, callback){ 138 | var completed = 0, 139 | calledback = false, 140 | sentBytes = 0, 141 | self = this; 142 | 143 | if(sampleRate && typeof sampleRate !== 'number'){ 144 | callback = tags; 145 | tags = sampleRate; 146 | sampleRate = undefined; 147 | } 148 | 149 | if(tags && !Array.isArray(tags)){ 150 | callback = tags; 151 | tags = undefined; 152 | } 153 | 154 | /** 155 | * Gets called once for each callback, when all callbacks return we will 156 | * call back from the function 157 | * @private 158 | */ 159 | function onSend(error, bytes){ 160 | completed += 1; 161 | if(calledback || typeof callback !== 'function'){ 162 | return; 163 | } 164 | 165 | if(error){ 166 | calledback = true; 167 | return callback(error); 168 | } 169 | 170 | sentBytes += bytes; 171 | if(completed === stat.length){ 172 | callback(null, sentBytes); 173 | } 174 | } 175 | 176 | if(Array.isArray(stat)){ 177 | stat.forEach(function(item){ 178 | self.send(item, value, type, sampleRate, tags, onSend); 179 | }); 180 | } else { 181 | this.send(stat, value, type, sampleRate, tags, callback); 182 | } 183 | }; 184 | 185 | /** 186 | * Sends a stat across the wire 187 | * @param stat {String|Array} The stat(s) to send 188 | * @param value The value to send 189 | * @param type {String} The type of message to send to statsd 190 | * @param sampleRate {Number} The Number of times to sample (0 to 1) 191 | * @param tags {Array} The Array of tags to add to metrics 192 | * @param callback {Function=} Callback when message is done being delivered. Optional. 193 | */ 194 | Client.prototype.send = function (stat, value, type, sampleRate, tags, callback) { 195 | var message = this.prefix + stat + this.suffix + ':' + value + '|' + type, 196 | buf, 197 | merged_tags = []; 198 | 199 | if(sampleRate && sampleRate < 1){ 200 | if(Math.random() < sampleRate){ 201 | message += '|@' + sampleRate; 202 | } else { 203 | //don't want to send if we don't meet the sample ratio 204 | return; 205 | } 206 | } 207 | 208 | if(tags && Array.isArray(tags)){ 209 | merged_tags = merged_tags.concat(tags); 210 | } 211 | if(this.global_tags && Array.isArray(this.global_tags)){ 212 | merged_tags = merged_tags.concat(this.global_tags); 213 | } 214 | if(merged_tags.length > 0){ 215 | message += '|#' + merged_tags.join(','); 216 | } 217 | 218 | // Only send this stat if we're not a mock Client. 219 | if(!this.mock) { 220 | buf = new Buffer(message); 221 | this.socket.send(buf, 0, buf.length, this.port, this.host, callback); 222 | } else { 223 | if(typeof callback === 'function'){ 224 | callback(null, 0); 225 | } 226 | } 227 | }; 228 | 229 | /** 230 | * Close the underlying socket and stop listening for data on it. 231 | */ 232 | Client.prototype.close = function(){ 233 | this.socket.close(); 234 | } 235 | 236 | exports = module.exports = Client; 237 | exports.StatsD = Client; 238 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { "name" : "node-statsd" 2 | , "description" : "node client for Etsy'd StatsD server" 3 | , "version" : "0.1.1" 4 | , "author" : "Steve Ivy" 5 | , "contributors": [ "Russ Bradberry " ] 6 | , "repository" : 7 | { "type" : "git" 8 | , "url" : "git://github.com/sivy/node-statsd.git" 9 | } 10 | , "bugs" : { "url" : "https://github.com/sivy/node-statsd/issues" } 11 | , "directories" : { "lib" : "./lib/" } 12 | , "engines" : { "node" : ">=0.1.97" } 13 | , "scripts": { 14 | "test": "mocha -R spec" 15 | } 16 | , "dependencies" : {} 17 | , "devDependencies": { 18 | "mocha":"*" 19 | } 20 | , "licenses" : 21 | [ { "type" : "MIT" 22 | , "url" : "http://github.com/sivy/node-stats/raw/master/LICENSE" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /test/test_statsd.js: -------------------------------------------------------------------------------- 1 | var dgram = require('dgram'), 2 | assert = require('assert'), 3 | StatsD = require('../').StatsD; 4 | 5 | /** 6 | * Creates a test harness, that binds to an ephemeral port 7 | * @param test {Function} The test to run, should take message as the argument 8 | * @param callback {Function} The callback to call after the server is listening 9 | * @private 10 | */ 11 | function udpTest(test, callback){ 12 | var server = dgram.createSocket("udp4"); 13 | server.on('message', function(message){ 14 | test(message.toString(), server); 15 | }); 16 | 17 | server.on('listening', function(){ 18 | callback(server); 19 | }); 20 | 21 | server.bind(0, '127.0.0.1'); 22 | } 23 | 24 | /** 25 | * Given a StatsD method, make sure no data is sent to the server 26 | * for this method when used on a mock Client. 27 | */ 28 | function assertMockClientMethod(method, finished){ 29 | var testFinished = "test finished message"; 30 | 31 | udpTest(function(message, server){ 32 | // We only expect to get our own test finished message, no stats. 33 | assert.equal(message, testFinished); 34 | server.close(); 35 | finished(); 36 | }, function(server){ 37 | var address = server.address(), 38 | statsd = new StatsD(address.address, address.port, 'prefix', 'suffix', false, false, 39 | /* mock = true */ true), 40 | socket = dgram.createSocket("udp4"), 41 | buf = new Buffer(testFinished), 42 | callbackThrows = false; 43 | 44 | // Regression test for "undefined is not a function" with missing callback on mock instance. 45 | try { 46 | statsd[method]('test', 1); 47 | } catch(e) { 48 | callbackThrows = true; 49 | } 50 | assert.ok(!callbackThrows); 51 | 52 | statsd[method]('test', 1, null, function(error, bytes){ 53 | assert.ok(!error); 54 | assert.equal(bytes, 0); 55 | // We should call finished() here, but we have to work around 56 | // https://github.com/joyent/node/issues/2867 on node 0.6, 57 | // such that we don't close the socket within the `listening` event 58 | // and pass a single message through instead. 59 | socket.send(buf, 0, buf.length, address.port, address.address, 60 | function(){ socket.close(); }); 61 | }); 62 | }); 63 | } 64 | 65 | /** 66 | * Since sampling uses random, we need to patch Math.random() to always give 67 | * a consisten result 68 | */ 69 | var oldRandom = Math.random; 70 | Math.random = function(){ 71 | return 0.42; 72 | }; 73 | 74 | 75 | describe('StatsD', function(){ 76 | describe('#init', function(){ 77 | it('should set default values when not specified', function(){ 78 | // cachedDns isn't tested here; see below 79 | var statsd = new StatsD(); 80 | assert.equal(statsd.host, 'localhost'); 81 | assert.equal(statsd.port, 8125); 82 | assert.equal(statsd.prefix, ''); 83 | assert.equal(statsd.suffix, ''); 84 | assert.equal(global.statsd, undefined); 85 | assert.equal(statsd.mock, undefined); 86 | assert.deepEqual(statsd.global_tags, []); 87 | assert.ok(!statsd.mock); 88 | }); 89 | 90 | it('should set the proper values when specified', function(){ 91 | // cachedDns isn't tested here; see below 92 | var statsd = new StatsD('host', 1234, 'prefix', 'suffix', true, null, true, ['gtag']); 93 | assert.equal(statsd.host, 'host'); 94 | assert.equal(statsd.port, 1234); 95 | assert.equal(statsd.prefix, 'prefix'); 96 | assert.equal(statsd.suffix, 'suffix'); 97 | assert.equal(statsd, global.statsd); 98 | assert.equal(statsd.mock, true); 99 | assert.deepEqual(statsd.global_tags, ['gtag']); 100 | }); 101 | 102 | it('should set the proper values with options hash format', function(){ 103 | // cachedDns isn't tested here; see below 104 | var statsd = new StatsD({ 105 | host: 'host', 106 | port: 1234, 107 | prefix: 'prefix', 108 | suffix: 'suffix', 109 | globalize: true, 110 | mock: true, 111 | global_tags: ['gtag'] 112 | }); 113 | assert.equal(statsd.host, 'host'); 114 | assert.equal(statsd.port, 1234); 115 | assert.equal(statsd.prefix, 'prefix'); 116 | assert.equal(statsd.suffix, 'suffix'); 117 | assert.equal(statsd, global.statsd); 118 | assert.equal(statsd.mock, true); 119 | assert.deepEqual(statsd.global_tags, ['gtag']); 120 | }); 121 | 122 | it('should attempt to cache a dns record if dnsCache is specified', function(done){ 123 | var dns = require('dns'), 124 | originalLookup = dns.lookup, 125 | statsd; 126 | 127 | // replace the dns lookup function with our mock dns lookup 128 | dns.lookup = function(host, callback){ 129 | process.nextTick(function(){ 130 | dns.lookup = originalLookup; 131 | assert.equal(statsd.host, host); 132 | callback(null, '127.0.0.1', 4); 133 | assert.equal(statsd.host, '127.0.0.1'); 134 | done(); 135 | }); 136 | }; 137 | 138 | statsd = new StatsD({host: 'localhost', cacheDns: true}); 139 | }); 140 | 141 | it('should not attempt to cache a dns record if dnsCache is specified', function(done){ 142 | var dns = require('dns'), 143 | originalLookup = dns.lookup, 144 | statsd; 145 | 146 | // replace the dns lookup function with our mock dns lookup 147 | dns.lookup = function(host, callback){ 148 | assert.ok(false, 'StatsD constructor should not invoke dns.lookup when dnsCache is unspecified'); 149 | dns.lookup = originalLookup; 150 | }; 151 | 152 | statsd = new StatsD({host: 'localhost'}); 153 | process.nextTick(function(){ 154 | dns.lookup = originalLookup; 155 | done(); 156 | }); 157 | }); 158 | 159 | it('should create a global variable set to StatsD() when specified', function(){ 160 | var statsd = new StatsD('host', 1234, 'prefix', 'suffix', true); 161 | assert.ok(global.statsd instanceof StatsD); 162 | //remove it from the namespace to not fail other tests 163 | delete global.statsd; 164 | }); 165 | 166 | it('should not create a global variable when not specified', function(){ 167 | var statsd = new StatsD('host', 1234, 'prefix', 'suffix'); 168 | assert.equal(global.statsd, undefined); 169 | }); 170 | 171 | it('should create a mock Client when mock variable is specified', function(){ 172 | var statsd = new StatsD('host', 1234, 'prefix', 'suffix', false, false, true); 173 | assert.ok(statsd.mock); 174 | }); 175 | 176 | it('should create a socket variable that is an instance of dgram.Socket', function(){ 177 | var statsd = new StatsD(); 178 | assert.ok(statsd.socket instanceof dgram.Socket); 179 | }); 180 | 181 | }); 182 | 183 | describe('#global_tags', function(){ 184 | it('should not add global tags if they are not specified', function(finished){ 185 | udpTest(function(message, server){ 186 | assert.equal(message, 'test:1|c'); 187 | server.close(); 188 | finished(); 189 | }, function(server){ 190 | var address = server.address(), 191 | statsd = new StatsD(address.address, address.port); 192 | 193 | statsd.increment('test'); 194 | }); 195 | }); 196 | 197 | it('should add global tags if they are specified', function(finished){ 198 | udpTest(function(message, server){ 199 | assert.equal(message, 'test:1|c|#gtag'); 200 | server.close(); 201 | finished(); 202 | }, function(server){ 203 | var address = server.address(), 204 | statsd = new StatsD({ 205 | host: address.address, 206 | port: address.port, 207 | global_tags: ['gtag'] 208 | }); 209 | 210 | statsd.increment('test'); 211 | }); 212 | }); 213 | 214 | it('should combine global tags and metric tags', function(finished){ 215 | udpTest(function(message, server){ 216 | assert.equal(message, 'test:1337|c|#foo,gtag'); 217 | server.close(); 218 | finished(); 219 | }, function(server){ 220 | var address = server.address(), 221 | statsd = new StatsD({ 222 | host: address.address, 223 | port: address.port, 224 | global_tags: ['gtag'] 225 | }); 226 | 227 | statsd.increment('test', 1337, ['foo']); 228 | }); 229 | }); 230 | }); 231 | 232 | describe('#timing', function(finished){ 233 | it('should send proper time format without prefix, suffix, sampling and callback', function(finished){ 234 | udpTest(function(message, server){ 235 | assert.equal(message, 'test:42|ms'); 236 | server.close(); 237 | finished(); 238 | }, function(server){ 239 | var address = server.address(), 240 | statsd = new StatsD(address.address, address.port); 241 | 242 | statsd.timing('test', 42); 243 | }); 244 | }); 245 | 246 | it('should send proper time format with tags', function(finished){ 247 | udpTest(function(message, server){ 248 | assert.equal(message, 'test:42|ms|#foo,bar'); 249 | server.close(); 250 | finished(); 251 | }, function(server){ 252 | var address = server.address(), 253 | statsd = new StatsD(address.address, address.port); 254 | 255 | statsd.timing('test', 42, ['foo', 'bar']); 256 | }); 257 | }); 258 | 259 | it('should send proper time format with prefix, suffix, sampling and callback', function(finished){ 260 | var called = false; 261 | udpTest(function(message, server){ 262 | assert.equal(message, 'foo.test.bar:42|ms|@0.5'); 263 | assert.equal(called, true); 264 | server.close(); 265 | finished(); 266 | }, function(server){ 267 | var address = server.address(), 268 | statsd = new StatsD(address.address, address.port, 'foo.', '.bar'); 269 | 270 | statsd.timing('test', 42, 0.5, function(){ 271 | called = true; 272 | }); 273 | }); 274 | }); 275 | 276 | it('should properly send a and b with the same value', function(finished){ 277 | var called = false, 278 | messageNumber = 0; 279 | 280 | udpTest(function(message, server){ 281 | if(messageNumber === 0){ 282 | assert.equal(message, 'a:42|ms'); 283 | messageNumber += 1; 284 | } else { 285 | assert.equal(message, 'b:42|ms'); 286 | server.close(); 287 | finished(); 288 | } 289 | }, function(server){ 290 | var address = server.address(), 291 | statsd = new StatsD(address.address, address.port); 292 | 293 | statsd.timing(['a', 'b'], 42, null, function(error, bytes){ 294 | called += 1; 295 | assert.ok(called === 1); //ensure it only gets called once 296 | assert.equal(error, null); 297 | assert.equal(bytes, 14); 298 | }); 299 | }); 300 | }); 301 | 302 | it('should send no timing stat when a mock Client is used', function(finished){ 303 | assertMockClientMethod('timing', finished); 304 | }); 305 | }); 306 | 307 | describe('#histogram', function(finished){ 308 | it('should send proper histogram format without prefix, suffix, sampling and callback', function(finished){ 309 | udpTest(function(message, server){ 310 | assert.equal(message, 'test:42|h'); 311 | server.close(); 312 | finished(); 313 | }, function(server){ 314 | var address = server.address(), 315 | statsd = new StatsD(address.address, address.port); 316 | 317 | statsd.histogram('test', 42); 318 | }); 319 | }); 320 | 321 | it('should send proper histogram format with tags', function(finished){ 322 | udpTest(function(message, server){ 323 | assert.equal(message, 'test:42|h|#foo,bar'); 324 | server.close(); 325 | finished(); 326 | }, function(server){ 327 | var address = server.address(), 328 | statsd = new StatsD(address.address, address.port); 329 | 330 | statsd.histogram('test', 42, ['foo', 'bar']); 331 | }); 332 | }); 333 | 334 | it('should send proper histogram format with prefix, suffix, sampling and callback', function(finished){ 335 | var called = false; 336 | udpTest(function(message, server){ 337 | assert.equal(message, 'foo.test.bar:42|h|@0.5'); 338 | assert.equal(called, true); 339 | server.close(); 340 | finished(); 341 | }, function(server){ 342 | var address = server.address(), 343 | statsd = new StatsD(address.address, address.port, 'foo.', '.bar'); 344 | 345 | statsd.histogram('test', 42, 0.5, function(){ 346 | called = true; 347 | }); 348 | }); 349 | }); 350 | 351 | it('should properly send a and b with the same value', function(finished){ 352 | var called = 0, 353 | messageNumber = 0; 354 | 355 | udpTest(function(message, server){ 356 | if(messageNumber === 0){ 357 | assert.equal(message, 'a:42|h'); 358 | messageNumber += 1; 359 | } else { 360 | assert.equal(message, 'b:42|h'); 361 | server.close(); 362 | finished(); 363 | } 364 | }, function(server){ 365 | var address = server.address(), 366 | statsd = new StatsD(address.address, address.port); 367 | 368 | statsd.histogram(['a', 'b'], 42, null, function(error, bytes){ 369 | called += 1; 370 | assert.ok(called === 1); //ensure it only gets called once 371 | assert.equal(error, null); 372 | assert.equal(bytes, 12); 373 | }); 374 | }); 375 | }); 376 | 377 | it('should send no histogram stat when a mock Client is used', function(finished){ 378 | assertMockClientMethod('histogram', finished); 379 | }); 380 | }); 381 | 382 | describe('#gauge', function(finished){ 383 | it('should send proper gauge format without prefix, suffix, sampling and callback', function(finished){ 384 | udpTest(function(message, server){ 385 | assert.equal(message, 'test:42|g'); 386 | server.close(); 387 | finished(); 388 | }, function(server){ 389 | var address = server.address(), 390 | statsd = new StatsD(address.address, address.port); 391 | 392 | statsd.gauge('test', 42); 393 | }); 394 | }); 395 | 396 | it('should send proper gauge format with tags', function(finished){ 397 | udpTest(function(message, server){ 398 | assert.equal(message, 'test:42|g|#foo,bar'); 399 | server.close(); 400 | finished(); 401 | }, function(server){ 402 | var address = server.address(), 403 | statsd = new StatsD(address.address, address.port); 404 | 405 | statsd.gauge('test', 42, ['foo', 'bar']); 406 | }); 407 | }); 408 | 409 | it('should send proper gauge format with prefix, suffix, sampling and callback', function(finished){ 410 | var called = false; 411 | udpTest(function(message, server){ 412 | assert.equal(message, 'foo.test.bar:42|g|@0.5'); 413 | assert.equal(called, true); 414 | server.close(); 415 | finished(); 416 | }, function(server){ 417 | var address = server.address(), 418 | statsd = new StatsD(address.address, address.port, 'foo.', '.bar'); 419 | 420 | statsd.gauge('test', 42, 0.5, function(){ 421 | called = true; 422 | }); 423 | }); 424 | }); 425 | 426 | it('should properly send a and b with the same value', function(finished){ 427 | var called = 0, 428 | messageNumber = 0; 429 | 430 | udpTest(function(message, server){ 431 | if(messageNumber === 0){ 432 | assert.equal(message, 'a:42|g'); 433 | messageNumber += 1; 434 | } else { 435 | assert.equal(message, 'b:42|g'); 436 | server.close(); 437 | finished(); 438 | } 439 | }, function(server){ 440 | var address = server.address(), 441 | statsd = new StatsD(address.address, address.port); 442 | 443 | statsd.gauge(['a', 'b'], 42, null, function(error, bytes){ 444 | called += 1; 445 | assert.ok(called === 1); //ensure it only gets called once 446 | assert.equal(error, null); 447 | assert.equal(bytes, 12); 448 | }); 449 | }); 450 | }); 451 | 452 | it('should send no gauge stat when a mock Client is used', function(finished){ 453 | assertMockClientMethod('gauge', finished); 454 | }); 455 | }); 456 | 457 | describe('#increment', function(finished){ 458 | it('should send count by 1 when no params are specified', function(finished){ 459 | udpTest(function(message, server){ 460 | assert.equal(message, 'test:1|c'); 461 | server.close(); 462 | finished(); 463 | }, function(server){ 464 | var address = server.address(), 465 | statsd = new StatsD(address.address, address.port); 466 | 467 | statsd.increment('test'); 468 | }); 469 | }); 470 | 471 | it('should send proper count format with tags', function(finished){ 472 | udpTest(function(message, server){ 473 | assert.equal(message, 'test:42|c|#foo,bar'); 474 | server.close(); 475 | finished(); 476 | }, function(server){ 477 | var address = server.address(), 478 | statsd = new StatsD(address.address, address.port); 479 | 480 | statsd.increment('test', 42, ['foo', 'bar']); 481 | }); 482 | }); 483 | 484 | it('should send proper count format with prefix, suffix, sampling and callback', function(finished){ 485 | var called = false; 486 | udpTest(function(message, server){ 487 | assert.equal(message, 'foo.test.bar:42|c|@0.5'); 488 | assert.equal(called, true); 489 | server.close(); 490 | finished(); 491 | }, function(server){ 492 | var address = server.address(), 493 | statsd = new StatsD(address.address, address.port, 'foo.', '.bar'); 494 | 495 | statsd.increment('test', 42, 0.5, function(){ 496 | called = true; 497 | }); 498 | }); 499 | }); 500 | 501 | it('should properly send a and b with the same value', function(finished){ 502 | var called = 0, 503 | messageNumber = 0; 504 | 505 | udpTest(function(message, server){ 506 | if(messageNumber === 0){ 507 | assert.equal(message, 'a:1|c'); 508 | messageNumber += 1; 509 | } else { 510 | assert.equal(message, 'b:1|c'); 511 | server.close(); 512 | finished(); 513 | } 514 | }, function(server){ 515 | var address = server.address(), 516 | statsd = new StatsD(address.address, address.port); 517 | 518 | statsd.increment(['a', 'b'], null, function(error, bytes){ 519 | called += 1; 520 | assert.ok(called === 1); //ensure it only gets called once 521 | assert.equal(error, null); 522 | assert.equal(bytes, 10); 523 | }); 524 | }); 525 | }); 526 | 527 | it('should send no increment stat when a mock Client is used', function(finished){ 528 | assertMockClientMethod('increment', finished); 529 | }); 530 | }); 531 | 532 | describe('#decrement', function(finished){ 533 | it('should send count by -1 when no params are specified', function(finished){ 534 | udpTest(function(message, server){ 535 | assert.equal(message, 'test:-1|c'); 536 | server.close(); 537 | finished(); 538 | }, function(server){ 539 | var address = server.address(), 540 | statsd = new StatsD(address.address, address.port); 541 | 542 | statsd.decrement('test'); 543 | }); 544 | }); 545 | 546 | it('should send proper count format with tags', function(finished){ 547 | udpTest(function(message, server){ 548 | assert.equal(message, 'test:-42|c|#foo,bar'); 549 | server.close(); 550 | finished(); 551 | }, function(server){ 552 | var address = server.address(), 553 | statsd = new StatsD(address.address, address.port); 554 | 555 | statsd.decrement('test', 42, ['foo', 'bar']); 556 | }); 557 | }); 558 | 559 | it('should send proper count format with prefix, suffix, sampling and callback', function(finished){ 560 | var called = false; 561 | udpTest(function(message, server){ 562 | assert.equal(message, 'foo.test.bar:-42|c|@0.5'); 563 | assert.equal(called, true); 564 | server.close(); 565 | finished(); 566 | }, function(server){ 567 | var address = server.address(), 568 | statsd = new StatsD(address.address, address.port, 'foo.', '.bar'); 569 | 570 | statsd.decrement('test', 42, 0.5, function(){ 571 | called = true; 572 | }); 573 | }); 574 | }); 575 | 576 | 577 | it('should properly send a and b with the same value', function(finished){ 578 | var called = 0, 579 | messageNumber = 0; 580 | 581 | udpTest(function(message, server){ 582 | if(messageNumber === 0){ 583 | assert.equal(message, 'a:-1|c'); 584 | messageNumber += 1; 585 | } else { 586 | assert.equal(message, 'b:-1|c'); 587 | server.close(); 588 | finished(); 589 | } 590 | }, function(server){ 591 | var address = server.address(), 592 | statsd = new StatsD(address.address, address.port); 593 | 594 | statsd.decrement(['a', 'b'], null, function(error, bytes){ 595 | called += 1; 596 | assert.ok(called === 1); //ensure it only gets called once 597 | assert.equal(error, null); 598 | assert.equal(bytes, 12); 599 | }); 600 | }); 601 | }); 602 | 603 | it('should send no decrement stat when a mock Client is used', function(finished){ 604 | assertMockClientMethod('decrement', finished); 605 | }); 606 | }); 607 | 608 | describe('#set', function(finished){ 609 | it('should send proper set format without prefix, suffix, sampling and callback', function(finished){ 610 | udpTest(function(message, server){ 611 | assert.equal(message, 'test:42|s'); 612 | server.close(); 613 | finished(); 614 | }, function(server){ 615 | var address = server.address(), 616 | statsd = new StatsD(address.address, address.port); 617 | 618 | statsd.set('test', 42); 619 | }); 620 | }); 621 | 622 | it('should send proper set format with tags', function(finished){ 623 | udpTest(function(message, server){ 624 | assert.equal(message, 'test:42|s|#foo,bar'); 625 | server.close(); 626 | finished(); 627 | }, function(server){ 628 | var address = server.address(), 629 | statsd = new StatsD(address.address, address.port); 630 | 631 | statsd.set('test', 42, ['foo', 'bar']); 632 | }); 633 | }); 634 | 635 | it('should send proper set format with prefix, suffix, sampling and callback', function(finished){ 636 | var called = false; 637 | udpTest(function(message, server){ 638 | assert.equal(message, 'foo.test.bar:42|s|@0.5'); 639 | assert.equal(called, true); 640 | server.close(); 641 | finished(); 642 | }, function(server){ 643 | var address = server.address(), 644 | statsd = new StatsD(address.address, address.port, 'foo.', '.bar'); 645 | 646 | statsd.unique('test', 42, 0.5, function(){ 647 | called = true; 648 | }); 649 | }); 650 | }); 651 | 652 | it('should properly send a and b with the same value', function(finished){ 653 | var called = 0, 654 | messageNumber = 0; 655 | 656 | udpTest(function(message, server){ 657 | if(messageNumber === 0){ 658 | assert.equal(message, 'a:42|s'); 659 | messageNumber += 1; 660 | } else { 661 | assert.equal(message, 'b:42|s'); 662 | server.close(); 663 | finished(); 664 | } 665 | }, function(server){ 666 | var address = server.address(), 667 | statsd = new StatsD(address.address, address.port); 668 | 669 | statsd.unique(['a', 'b'], 42, null, function(error, bytes){ 670 | called += 1; 671 | assert.ok(called === 1); //ensure it only gets called once 672 | assert.equal(error, null); 673 | assert.equal(bytes, 12); 674 | }); 675 | }); 676 | }); 677 | 678 | it('should send no set stat when a mock Client is used', function(finished){ 679 | assertMockClientMethod('set', finished); 680 | }); 681 | }); 682 | 683 | }); 684 | --------------------------------------------------------------------------------