From 131deb7c06c490cd1a894ca07f70aca7d421f452 Mon Sep 17 00:00:00 2001 From: Sami Samhuri Date: Tue, 4 Jun 2013 10:31:48 -0700 Subject: [PATCH] first commit --- .gitignore | 1 + Readme.md | 27 ++++++++++++++++++ bin/kwikemon | 10 +++++++ bin/kwikemond | 8 ++++++ kwikemon.js | 76 +++++++++++++++++++++++++++++++++++++++++++++++++ line_emitter.js | 54 +++++++++++++++++++++++++++++++++++ package.json | 20 +++++++++++++ server.js | 57 +++++++++++++++++++++++++++++++++++++ 8 files changed, 253 insertions(+) create mode 100644 .gitignore create mode 100644 Readme.md create mode 100755 bin/kwikemon create mode 100755 bin/kwikemond create mode 100644 kwikemon.js create mode 100644 line_emitter.js create mode 100644 package.json create mode 100644 server.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..401fc59 --- /dev/null +++ b/Readme.md @@ -0,0 +1,27 @@ +# kwik-e-mon + +Monitor one-off tasks on your servers. + + +## Installation + +npm install -g kwikemon + + +## Usage + +I can't think of a concise example use of the command line tool. Here's how it works: + +- you continuously pipe data to `kwikemon ` on stdin +- every time a full line of text is received on stdin it becomes the new status for +- there's a simple web server, `kwikemond`, that serves up these monitors in a big list or individually + +This is very much a work in progress and as the functionality is fleshed out this readme will improve as well. + + +## License + +Copyright 2013 Sami Samhuri + +[MIT license](http://sjs.mit-license.org) + diff --git a/bin/kwikemon b/bin/kwikemon new file mode 100755 index 0000000..8c41e85 --- /dev/null +++ b/bin/kwikemon @@ -0,0 +1,10 @@ +#!/usr/bin/env node + +var kwikemon = require('../kwikemon.js') + , name = process.argv[2] + ; + +process.stdin.pipe(kwikemon.createWriter(name)); +process.stdin.on('end', function() { + process.exit(0); +}); diff --git a/bin/kwikemond b/bin/kwikemond new file mode 100755 index 0000000..f6d420f --- /dev/null +++ b/bin/kwikemond @@ -0,0 +1,8 @@ +#!/usr/bin/env node + +var server = require('../server.js') + , port = process.argv[2] + , host = process.argv[3] + ; + +server.start(port, host); diff --git a/kwikemon.js b/kwikemon.js new file mode 100644 index 0000000..8c75ec4 --- /dev/null +++ b/kwikemon.js @@ -0,0 +1,76 @@ +// Copyright 2013 Sami Samhuri + +module.exports = { + // read + fetchMonitor: fetchMonitor +, fetchMonitors: fetchMonitors + + // write +, monitor: monitor +, createWriter: createWriter +}; + +var redis = require('redis').createClient() + , LineEmitter = require('./line_emitter.js') + ; + +function monitor(name, text, options) { + console.log(name,'=',text) + options = options || {}; + if (typeof options == 'function') { + options = { cb: options }; + } + var key = 'kwikemon:monitor:' + name + , timeout = options.timeout || 86400 + ; + console.log('set',key,text) + redis.set(key, text, function(err, status) { + console.log('set',key,text) + if (err) throw err; + if (timeout >= 0) { + redis.expire(key, timeout); + } + redis.sadd('kwikemon:monitors', name, function(err, status) { + if (options.cb) options.cb(); + }); + }); +} + +function createWriter(name) { + var le = new LineEmitter(); + le.on('line', function(line) { + monitor(name, line); + }); + return le; +} + +function fetchMonitor(name, cb) { + redis.get('kwikemon:monitor:' + name, cb); +} + +function fetchMonitors(cb) { + var monitors = {} + , i = 0 + , n + , checkIfDone = function() { + i += 1; + if (i == n) cb(null, monitors); + } + ; + redis.smembers('kwikemon:monitors', function(err, names) { + if (err) return cb(err); + n = names.length; + names.forEach(function(name) { + fetchMonitor(name, function(err, text) { + if (err) { + // missing? probably don't care + } + else { + monitors[name] = text; + } + checkIfDone(); + }); + }); + }); +} + diff --git a/line_emitter.js b/line_emitter.js new file mode 100644 index 0000000..c13d648 --- /dev/null +++ b/line_emitter.js @@ -0,0 +1,54 @@ +// Copyright 2013 Sami Samhuri + +module.exports = LineEmitter; + +var stream = require('stream') + , Transform = stream.Transform + ; + +function LineEmitter(options) { + Transform.call(this, options); + this._buffer = []; +} + +LineEmitter.prototype = Object.create(Transform.prototype, { + constructor: { value: LineEmitter } +}); + +LineEmitter.prototype._transform = function(chunk, encoding, done) { + // check for a newline + var split = -1; + for (var i = 0; i < chunk.length; i++) { + if (chunk[i] === 10) { // '\n' + split = i; + break; + } + } + + // buffer until we see a newline + if (split == -1) { + this._buffer.push(chunk); + } + + // construct & emit the line, buffering the rest of the next line + else { + this._buffer.push(chunk.slice(0, split)); + var line = Buffer.concat(this._buffer).toString(); + this.emit('line', line); + + // skip over newline + this._buffer = [chunk.slice(split + 1)]; + } + + // no actual transform + this.push(chunk); + done(); +} + +LineEmitter.prototype._flush = function(cb) { + var line = Buffer.concat(this._buffer).toString(); + if (line) { + this.emit('line', line); + } + this._buffer = []; +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6ea2b64 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ "name": "kwikemon" +, "version": "0.0.1" +, "description": "monitor one-off things on your servers" +, "author": "Sami Samhuri " +, "license": "MIT" +, "repository": "https://github.com/samsonjs/kwikemon" +, "keywords": [ "monitor", "server", "watch", "one-off", "task" ] +, "main": "kwikemon.js" +, "bin": { + "kwikemon": "./bin/kwikemon" + , "kwikemond": "./bin/kwikemond" + } +, "dependencies": { + "paramify": "0.0.x" + , "redis": "0.8.x" + } +, "engines": { + "node": ">=0.10" + } +} \ No newline at end of file diff --git a/server.js b/server.js new file mode 100644 index 0000000..17f3fcf --- /dev/null +++ b/server.js @@ -0,0 +1,57 @@ +// Copyright 2013 Sami Samhuri + +module.exports = { + create: create +, start: start +, stop: stop +}; + +var http = require('http') + , paramify = require('paramify') + , kwikemon = require('./kwikemon.js') + , _server + ; + +function create() { + return http.createServer(handleRequest); +} + +function start(port, host) { + port = port || 1111; + host = host || '127.0.0.1'; + _server = create(); + _server.listen(port, host); + console.log('kwikemond listening on ' + host + ':' + port); + return _server; +} + +function stop() { + _server.close(); + _server = null; +} + +function handleRequest(req, res) { + var name = req.url.replace(/^\//, ''); + if (name) { + kwikemon.fetchMonitor(name, function(err, text) { + if (err) { + res.end('error: ' + (err.message || 'unknown')); + return; + } + res.end(text); + }); + } + // all + else { + kwikemon.fetchMonitors(function(err, monitors) { + if (err) { + res.end('error: ' + (err.message || 'unknown')); + return; + } + Object.keys(monitors).sort().forEach(function(name) { + res.write(name + ': ' + monitors[name] + '\n'); + }); + res.end(); + }); + } +}