munin-client
Version:
Munin client for Node
238 lines (210 loc) • 5.94 kB
JavaScript
var net = require('net');
// these don't have a real meaning; they just need to be unique
// and constants are smarter than arbitrary strings
const BANNER = 1;
const LIST = 2;
const FETCH = 3;
const VERSION = 4;
const NODES = 5;
const CONFIG = 6;
const QUIT = 255;
// empty function to use as a default for callbacks
var nocb = function(){};
var Munin = function (host, port) {
this.host = host;
this.port = port || 4949;
this.connecting = false;
this.connection = undefined;
this.preConnectBuffer = '';
this.commandFifo = [{cmd: BANNER, callback: nocb}];
this.commandBuf = undefined;
this.closeAfterFifo = false;
this.hasQuit = false;
};
Munin.prototype = {
connect: function (callback) {
callback = callback || nocb;
this.connecting = true;
munin = this;
var client = net.connect(
{port: this.port, host: this.host},
function() { //'connect' listener
// no longer connecting
this.connecting = false;
munin.connection = client;
if (munin.preConnectBuffer) {
// if we've buffered up commands before the connection happened,
// drain that buffer into the socket
client.write(munin.preConnectBuffer);
}
callback(client);
}
);
client.on('data', function (data) {
// fetch data from the socket; this happens to always be in chunks
// ending in \n for Munin, with my testing, so this makes things
// slightly easier
data.toString().split('\n').forEach(function (str) {
// ignore empty lines
if (str !== "") {
munin.dataHandler(str);
}
});
});
client.on('end', function() {
munin.connection = false;
});
},
disconnect: function () {
this.closeAfterFifo = true;
},
connectIfNotConnected: function (callback) {
callback = callback || nocb;
// initiate a connection if one doesn't exist (and is not in progress)
if (!this.connection && !this.connecting) {
this.connect(callback);
} else {
callback(this.connection);
}
},
writeToSocket: function (str) {
munin.preConnectBuffer += str;
},
pushCommand: function(cmd, callback) {
if (munin.hasQuit) {
throw "Can't send command after `quit`";
}
munin.commandFifo.push({cmd: cmd, callback: callback});
},
commandComplete: function() {
munin.commandFifo.shift();
munin.commandBuf = undefined;
if (munin.commandFifo.length == 0 && munin.closeAfterFifo && munin.connection) {
munin.connection.end();
munin.closeAfterFifo = false;
}
},
dataHandler: function(data) {
if (!munin.commandFifo.length) {
throw "Wah-oh. No command in the FIFO.";
}
var currentCmd = munin.commandFifo[0];
switch (currentCmd.cmd) {
case BANNER:
// ignore the banner
munin.commandComplete();
break;
case LIST:
// split the list on space, return array
var list = data.trim().split(' ');
currentCmd.callback(list);
munin.commandComplete();
break;
case CONFIG:
// accumulate until "."
if (munin.commandBuf === undefined) {
// object to hold the command buffer
munin.commandBuf = {};
}
if (data == '.') {
currentCmd.callback(munin.commandBuf);
munin.commandComplete();
} else {
// collect data
var parts = data.split(' ', 2);
if (parts[0].indexOf('.') == -1) {
// if it doesn't contains a dot, that is the name
munin.commandBuf[parts[0]] = parts[1];
} else {
// but if it does, create a sub-object:
var name = parts[0].split('.', 2);
if (undefined === munin.commandBuf[name[0]]) {
munin.commandBuf[name[0]] = {};
}
munin.commandBuf[name[0]][name[1]] = parts[1];
}
}
break;
case FETCH:
// accumulate until "."
if (munin.commandBuf === undefined) {
// object to hold the command buffer
munin.commandBuf = {};
}
if (data == '.') {
currentCmd.callback(munin.commandBuf);
munin.commandComplete();
} else {
// collect data
var parts = data.split(' ');
var name = parts[0].split('.');
if (name[1] == 'value') {
// if foo.value, all we really want is foo
name = name[0];
} else {
// but if we get not-.value for some reason, keep it all
name = parts[0];
}
munin.commandBuf[name] = parts[1];
}
break;
case NODES:
// accumulate until "."
if (munin.commandBuf === undefined) {
// object to hold the command buffer
munin.commandBuf = [];
}
if (data == '.') {
currentCmd.callback(munin.commandBuf);
munin.commandComplete();
} else {
// collect data
munin.commandBuf.push(data);
}
break;
case VERSION:
// expect munins node on fkops02.prod.fictivevpn.com version: 1.4.5
var matches = data.match(/^munins node on (.*?) version: (.*)$/);
currentCmd.callback({node: matches[1], version: matches[2]});
munin.commandComplete();
break;
}
},
list: function (callback) {
callback = callback || nocb;
this.connectIfNotConnected();
this.pushCommand(LIST, callback);
this.writeToSocket('list\n');
},
config: function (metricName, callback) {
callback = callback || nocb;
this.connectIfNotConnected();
this.pushCommand(CONFIG, callback);
this.writeToSocket('config ' + metricName + '\n');
},
fetch: function (metricName, callback) {
callback = callback || nocb;
this.connectIfNotConnected();
this.pushCommand(FETCH, callback);
this.writeToSocket('fetch ' + metricName + '\n');
},
version: function (callback) {
callback = callback || nocb;
this.connectIfNotConnected();
this.pushCommand(VERSION, callback);
this.writeToSocket('version\n');
},
nodes: function (callback) {
callback = callback || nocb;
this.connectIfNotConnected();
this.pushCommand(NODES, callback);
this.writeToSocket('nodes\n');
},
quit: function () {
this.hasQuit = true;
if (this.connection || this.connecting) {
this.writeToSocket('quit\n');
}
}
};
module.exports = Munin;