2013-03-29 23:13:06 +04:00
|
|
|
var dgram = require('dgram')
|
|
|
|
, net = require('net')
|
|
|
|
, events = require('events')
|
2013-07-31 22:32:36 +04:00
|
|
|
, logger = require('./lib/logger')
|
2013-03-29 23:13:06 +04:00
|
|
|
, hashring = require('hashring')
|
2013-04-10 22:54:08 +04:00
|
|
|
, configlib = require('./lib/config');
|
2013-03-29 23:13:06 +04:00
|
|
|
|
|
|
|
var packet = new events.EventEmitter();
|
|
|
|
var node_status = [];
|
|
|
|
var node_ring = {};
|
2013-04-10 22:54:08 +04:00
|
|
|
var config;
|
2013-07-31 22:32:36 +04:00
|
|
|
var l; // logger
|
2013-03-29 23:13:06 +04:00
|
|
|
|
2013-04-10 22:54:08 +04:00
|
|
|
configlib.configFile(process.argv[2], function (conf, oldConfig) {
|
|
|
|
config = conf;
|
|
|
|
var udp_version = config.udp_version
|
|
|
|
, nodes = config.nodes;
|
2013-07-31 22:32:36 +04:00
|
|
|
l = new logger.Logger(config.log || {});
|
2013-03-29 23:13:06 +04:00
|
|
|
|
2013-04-10 22:54:08 +04:00
|
|
|
//load the node_ring object with the available nodes and a weight of 100
|
|
|
|
// weight is currently arbitrary but the same for all
|
|
|
|
nodes.forEach(function(element, index, array) {
|
|
|
|
node_ring[element.host + ':' + element.port] = 100;
|
2013-03-29 23:13:06 +04:00
|
|
|
});
|
|
|
|
|
2013-04-10 22:54:08 +04:00
|
|
|
var ring = new hashring(
|
|
|
|
node_ring, 'md5', {
|
|
|
|
'max cache size': config.cacheSize || 10000,
|
|
|
|
//We don't want duplicate keys sent so replicas set to 0
|
|
|
|
'replicas': 0
|
|
|
|
});
|
2013-03-29 23:13:06 +04:00
|
|
|
|
2013-04-10 22:54:08 +04:00
|
|
|
// Do an initial rount of health checks prior to starting up the server
|
|
|
|
doHealthChecks();
|
2013-03-29 23:13:06 +04:00
|
|
|
|
|
|
|
|
2013-04-10 22:54:08 +04:00
|
|
|
// Setup the udp listener
|
|
|
|
var server = dgram.createSocket(udp_version, function (msg, rinfo) {
|
|
|
|
// Convert the raw packet to a string (defaults to UTF8 encoding)
|
|
|
|
var packet_data = msg.toString();
|
|
|
|
// If the packet contains a \n then it contains multiple metrics
|
|
|
|
var metrics;
|
|
|
|
if (packet_data.indexOf("\n") > -1) {
|
|
|
|
metrics = packet_data.split("\n");
|
|
|
|
} else {
|
|
|
|
// metrics needs to be an array to fake it for single metric packets
|
|
|
|
metrics = [ packet_data ] ;
|
|
|
|
}
|
2013-03-29 23:13:06 +04:00
|
|
|
|
2013-04-10 22:54:08 +04:00
|
|
|
// Loop through the metrics and split on : to get mertric name for hashing
|
|
|
|
for (var midx in metrics) {
|
|
|
|
var bits = metrics[midx].toString().split(':');
|
|
|
|
var key = bits.shift();
|
|
|
|
packet.emit('send', key, msg);
|
|
|
|
}
|
2013-03-29 23:13:06 +04:00
|
|
|
});
|
|
|
|
|
2013-04-10 22:54:08 +04:00
|
|
|
// Listen for the send message, and process the metric key and msg
|
|
|
|
packet.on('send', function(key, msg) {
|
|
|
|
// retreives the destination for this key
|
|
|
|
var statsd_host = ring.get(key);
|
2013-03-29 23:13:06 +04:00
|
|
|
|
2013-04-10 22:54:08 +04:00
|
|
|
// break the retreived host to pass to the send function
|
2013-07-31 22:32:36 +04:00
|
|
|
if (statsd_host === undefined) {
|
|
|
|
l.log('Warning: No backend statsd nodes available!');
|
|
|
|
} else {
|
|
|
|
var host_config = statsd_host.split(':');
|
2013-03-29 23:13:06 +04:00
|
|
|
|
2013-07-31 22:32:36 +04:00
|
|
|
var client = dgram.createSocket(udp_version);
|
|
|
|
// Send the mesg to the backend
|
|
|
|
client.send(msg, 0, msg.length, host_config[1], host_config[0], function(err, bytes) {
|
|
|
|
client.close();
|
|
|
|
});
|
|
|
|
}
|
2013-03-29 23:13:06 +04:00
|
|
|
});
|
2013-04-10 22:54:08 +04:00
|
|
|
|
|
|
|
// Bind the listening udp server to the configured port and host
|
|
|
|
server.bind(config.port, config.host || undefined);
|
|
|
|
|
|
|
|
// Set the interval for healthchecks
|
|
|
|
setInterval(doHealthChecks, config.checkInterval || 10000);
|
|
|
|
|
|
|
|
// Perform health check on all nodes
|
|
|
|
function doHealthChecks() {
|
|
|
|
nodes.forEach(function(element, index, array) {
|
|
|
|
healthcheck(element);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Perform health check on node
|
|
|
|
function healthcheck(node) {
|
|
|
|
var node_id = node.host + ':' + node.port;
|
|
|
|
var client = net.connect({port: node.adminport, host: node.host},
|
|
|
|
function() {
|
|
|
|
client.write('health\r\n');
|
|
|
|
});
|
|
|
|
client.on('data', function(data) {
|
|
|
|
var health_status = data.toString();
|
|
|
|
client.end();
|
|
|
|
if (health_status.indexOf('up') < 0) {
|
|
|
|
if (node_status[node_id] === undefined) {
|
|
|
|
node_status[node_id] = 1;
|
|
|
|
} else {
|
|
|
|
node_status[node_id]++;
|
|
|
|
}
|
|
|
|
if (node_status[node_id] < 2) {
|
2013-07-31 22:32:36 +04:00
|
|
|
l.log('Removing node ' + node_id + ' from the ring.');
|
2013-04-10 22:54:08 +04:00
|
|
|
ring.remove(node_id);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (node_status[node_id] !== undefined) {
|
2013-08-01 08:17:27 +04:00
|
|
|
if (node_status[node_id] > 0) {
|
|
|
|
var new_server = {};
|
|
|
|
new_server[node_id] = 100;
|
|
|
|
l.log('Adding node ' + node_id + ' to the ring.');
|
|
|
|
ring.add(new_server);
|
|
|
|
}
|
2013-04-10 22:54:08 +04:00
|
|
|
}
|
|
|
|
node_status[node_id] = 0;
|
2013-03-29 23:13:06 +04:00
|
|
|
}
|
2013-04-10 22:54:08 +04:00
|
|
|
});
|
|
|
|
client.on('error', function(e) {
|
|
|
|
if (e.code == 'ECONNREFUSED') {
|
|
|
|
if (node_status[node_id] === undefined) {
|
|
|
|
node_status[node_id] = 1;
|
|
|
|
} else {
|
|
|
|
node_status[node_id]++;
|
|
|
|
}
|
|
|
|
if (node_status[node_id] < 2) {
|
2013-07-31 22:32:36 +04:00
|
|
|
l.log('Removing node ' + node_id + ' from the ring.');
|
2013-04-10 22:54:08 +04:00
|
|
|
ring.remove(node_id);
|
|
|
|
}
|
|
|
|
} else {
|
2013-08-01 08:17:27 +04:00
|
|
|
l.log('Error during healthcheck on node ' + node_id + ' with ' + e.code);
|
2013-04-10 22:54:08 +04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|