Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into selective-iface
- Loading branch information
Showing
7 changed files
with
303 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,30 @@ | ||
/* | ||
Required Variables: | ||
port: StatsD listening port [default: 8125] | ||
Graphite Required Variables: | ||
(Leave these unset to avoid sending stats to Graphite. | ||
Set debug flag and leave these unset to run in 'dry' debug mode - | ||
useful for testing statsd clients without a Graphite server.) | ||
graphiteHost: hostname or IP of Graphite server | ||
graphitePort: port of Graphite server | ||
Optional Variables: | ||
debug: debug flag [default: false] | ||
debugInterval: interval to print debug information [ms, default: 10000] | ||
dumpMessages: log all incoming messages | ||
flushInterval: interval (in ms) to flush to Graphite | ||
percentThreshold: for time information, calculate the Nth percentile | ||
[%, default: 90] | ||
*/ | ||
{ | ||
graphitePort: 2003 | ||
, graphiteHost: "graphite.host.com" | ||
, port: 8125 | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
#!/usr/bin/env node | ||
try { | ||
var reporter = require('nodeunit').reporters.default; | ||
} | ||
catch(e) { | ||
console.log("Cannot find nodeunit module."); | ||
console.log("Make sure to run 'npm install nodeunit'"); | ||
process.exit(); | ||
} | ||
|
||
process.chdir(__dirname); | ||
reporter.run(['test/']); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,227 @@ | ||
var fs = require('fs'), | ||
net = require('net'), | ||
temp = require('temp'), | ||
spawn = require('child_process').spawn, | ||
sys = require('sys'), | ||
urlparse = require('url').parse, | ||
_ = require('underscore'), | ||
dgram = require('dgram'), | ||
qsparse = require('querystring').parse, | ||
http = require('http'); | ||
|
||
|
||
var writeconfig = function(text,worker,cb,obj){ | ||
temp.open({suffix: '-statsdconf.js'}, function(err, info) { | ||
if (err) throw err; | ||
fs.write(info.fd, text); | ||
fs.close(info.fd, function(err) { | ||
if (err) throw err; | ||
worker(info.path,cb,obj); | ||
}); | ||
}); | ||
} | ||
|
||
var array_contents_are_equal = function(first,second){ | ||
var intlen = _.intersection(first,second).length; | ||
var unlen = _.union(first,second).length; | ||
return (intlen == unlen) && (intlen == first.length); | ||
} | ||
|
||
var statsd_send = function(data,sock,host,port,cb){ | ||
send_data = new Buffer(data); | ||
sock.send(send_data,0,send_data.length,port,host,function(err,bytes){ | ||
if (err) { | ||
throw err; | ||
} | ||
cb(); | ||
}); | ||
} | ||
|
||
// keep collecting data until a specified timeout period has elapsed | ||
// this will let us capture all data chunks so we don't miss one | ||
var collect_for = function(server,timeout,cb){ | ||
var received = []; | ||
var in_flight = 0; | ||
var start_time = new Date().getTime(); | ||
var collector = function(req,res){ | ||
in_flight += 1; | ||
var body = ''; | ||
req.on('data',function(data){ body += data; }); | ||
req.on('end',function(){ | ||
received = received.concat(body.split("\n")); | ||
in_flight -= 1; | ||
if((in_flight < 1) && (new Date().getTime() > (start_time + timeout))){ | ||
server.removeListener('request',collector); | ||
cb(received); | ||
} | ||
}); | ||
} | ||
|
||
setTimeout(function (){ | ||
server.removeListener('connection',collector); | ||
if((in_flight < 1)){ | ||
cb(received); | ||
} | ||
},timeout); | ||
|
||
server.on('connection',collector); | ||
} | ||
|
||
module.exports = { | ||
setUp: function (callback) { | ||
this.testport = 31337; | ||
this.myflush = 200; | ||
var configfile = "{graphService: \"graphite\"\n\ | ||
, batch: 200 \n\ | ||
, flushInterval: " + this.myflush + " \n\ | ||
, port: 8125\n\ | ||
, dumpMessages: false \n\ | ||
, debug: false\n\ | ||
, graphitePort: " + this.testport + "\n\ | ||
, graphiteHost: \"127.0.0.1\"}"; | ||
|
||
this.acceptor = net.createServer(); | ||
this.acceptor.listen(this.testport); | ||
this.sock = dgram.createSocket('udp4'); | ||
|
||
this.server_up = true; | ||
this.ok_to_die = false; | ||
this.exit_callback_callback = process.exit; | ||
|
||
writeconfig(configfile,function(path,cb,obj){ | ||
obj.path = path; | ||
obj.server = spawn('node',['stats.js', path]); | ||
obj.exit_callback = function (code) { | ||
obj.server_up = false; | ||
if(!obj.ok_to_die){ | ||
console.log('node server unexpectedly quit with code: ' + code); | ||
process.exit(1); | ||
} | ||
else { | ||
obj.exit_callback_callback(); | ||
} | ||
}; | ||
obj.server.on('exit', obj.exit_callback); | ||
obj.server.stderr.on('data', function (data) { | ||
console.log('stderr: ' + data.toString().replace(/\n$/,'')); | ||
}); | ||
/* | ||
obj.server.stdout.on('data', function (data) { | ||
console.log('stdout: ' + data.toString().replace(/\n$/,'')); | ||
}); | ||
*/ | ||
obj.server.stdout.on('data', function (data) { | ||
// wait until server is up before we finish setUp | ||
if (data.toString().match(/server is up/)) { | ||
cb(); | ||
} | ||
}); | ||
|
||
},callback,this); | ||
}, | ||
tearDown: function (callback) { | ||
this.sock.close(); | ||
this.acceptor.close(); | ||
this.ok_to_die = true; | ||
if(this.server_up){ | ||
this.exit_callback_callback = callback; | ||
this.server.kill(); | ||
} else { | ||
callback(); | ||
} | ||
}, | ||
|
||
send_well_formed_posts: function (test) { | ||
test.expect(2); | ||
|
||
// we should integrate a timeout into this | ||
this.acceptor.once('connection',function(c){ | ||
var body = ''; | ||
c.on('data',function(d){ body += d; }); | ||
c.on('end',function(){ | ||
var rows = body.split("\n"); | ||
var entries = _.map(rows, function(x) { | ||
var chunks = x.split(' '); | ||
var data = {}; | ||
data[chunks[0]] = chunks[1]; | ||
return data; | ||
}); | ||
test.ok(_.include(_.map(entries,function(x) { return _.keys(x)[0] }),'statsd.numStats'),'graphite output includes numStats'); | ||
test.equal(_.find(entries, function(x) { return _.keys(x)[0] == 'statsd.numStats' })['statsd.numStats'],0); | ||
test.done(); | ||
}); | ||
}); | ||
}, | ||
|
||
timers_are_valid: function (test) { | ||
test.expect(3); | ||
|
||
var testvalue = 100; | ||
var me = this; | ||
this.acceptor.once('connection',function(c){ | ||
statsd_send('a_test_value:' + testvalue + '|ms',me.sock,'127.0.0.1',8125,function(){ | ||
collect_for(me.acceptor,me.myflush*2,function(strings){ | ||
test.ok(strings.length > 0,'should receive some data'); | ||
var hashes = _.map(strings, function(x) { | ||
var chunks = x.split(' '); | ||
var data = {}; | ||
data[chunks[0]] = chunks[1]; | ||
return data; | ||
}); | ||
var numstat_test = function(post){ | ||
var mykey = 'statsd.numStats'; | ||
return _.include(_.keys(post),mykey) && (post[mykey] == 1); | ||
}; | ||
test.ok(_.any(hashes,numstat_test), 'statsd.numStats should be 1'); | ||
|
||
var testtimervalue_test = function(post){ | ||
var mykey = 'stats.timers.a_test_value.mean'; | ||
return _.include(_.keys(post),mykey) && (post[mykey] == testvalue); | ||
}; | ||
test.ok(_.any(hashes,testtimervalue_test), 'stats.timers.a_test_value.mean should be ' + testvalue); | ||
|
||
test.done(); | ||
}); | ||
}); | ||
}); | ||
}, | ||
|
||
counts_are_valid: function (test) { | ||
test.expect(4); | ||
|
||
var testvalue = 100; | ||
var me = this; | ||
this.acceptor.once('connection',function(c){ | ||
statsd_send('a_test_value:' + testvalue + '|c',me.sock,'127.0.0.1',8125,function(){ | ||
collect_for(me.acceptor,me.myflush*2,function(strings){ | ||
test.ok(strings.length > 0,'should receive some data'); | ||
var hashes = _.map(strings, function(x) { | ||
var chunks = x.split(' '); | ||
var data = {}; | ||
data[chunks[0]] = chunks[1]; | ||
return data; | ||
}); | ||
var numstat_test = function(post){ | ||
var mykey = 'statsd.numStats'; | ||
return _.include(_.keys(post),mykey) && (post[mykey] == 1); | ||
}; | ||
test.ok(_.any(hashes,numstat_test), 'statsd.numStats should be 1'); | ||
|
||
var testavgvalue_test = function(post){ | ||
var mykey = 'stats.a_test_value'; | ||
return _.include(_.keys(post),mykey) && (post[mykey] == (testvalue/(me.myflush / 1000))); | ||
}; | ||
test.ok(_.any(hashes,testavgvalue_test), 'stats.a_test_value should be ' + (testvalue/(me.myflush / 1000))); | ||
|
||
var testcountvalue_test = function(post){ | ||
var mykey = 'stats_counts.a_test_value'; | ||
return _.include(_.keys(post),mykey) && (post[mykey] == testvalue); | ||
}; | ||
test.ok(_.any(hashes,testcountvalue_test), 'stats_counts.a_test_value should be ' + testvalue); | ||
|
||
test.done(); | ||
}); | ||
}); | ||
}); | ||
} | ||
} |