Compare commits

..

21 commits

Author SHA1 Message Date
394fc04404 bug fix 2013-08-24 18:55:28 -07:00
f293afbc9e restructure to fix require path problems 2013-08-24 18:52:55 -07:00
096500ca82 bump ruby gem version 2013-08-24 18:46:54 -07:00
079d705280 clear gem's require path, clobbers built-in monitor 2013-08-24 18:46:10 -07:00
847d133594 more bug fixes 2013-08-24 18:20:56 -07:00
ecd8249271 bug fix 2013-08-24 18:10:19 -07:00
64b82dc978 bug fix 2013-08-24 18:08:32 -07:00
4bf4b09bc9 bump versions 2013-08-24 18:05:42 -07:00
5e978e94da bug fixes 2013-08-24 18:05:03 -07:00
d59674b60f v0.0.5 2013-08-24 17:25:30 -07:00
9776c1cfef support config file in Ruby client 2013-08-24 17:25:19 -07:00
7d398de64f fix Redis.createClient call & reading config 2013-08-24 17:18:33 -07:00
6cf290baf9 add ruby client library 2013-08-24 17:09:24 -07:00
672e1d1d0f v0.0.4 2013-07-14 17:43:59 -07:00
b9b49c0471 add simple HTML & text representations 2013-07-14 17:43:36 -07:00
36930d666d rename some commands & update readme 2013-06-08 22:26:03 -07:00
f8a09636d1 test setting ttl via kwikemon.ttl(name, ttl) 2013-06-08 22:16:03 -07:00
70fc6363ab add config file support for redis, rewrite command line client 2013-06-08 22:14:28 -07:00
a5d210439c v0.0.3 2013-06-08 16:17:56 -07:00
140e441919 rename fetchTTL to ttl 2013-06-08 16:17:42 -07:00
3fbf082cbe fix a bug creating a writer in bin/kwikemon 2013-06-08 16:17:41 -07:00
20 changed files with 747 additions and 242 deletions

View file

@ -11,28 +11,27 @@ npm install -g kwikemon
## Usage
$ kwikemond &
$ curl -s localhost/nginx_status | grep Active | kwikemon nginx-connections
$ curl -s localhost/nginx_status | grep Active | kwikemon write nginx-connections
$ curl localhost:1111/nignx-connections
Active connections: 316
$ kwikemon foo bar
$ kwikemon set foo bar
$ curl localhost:1111/
foo: bar
nginx-connections: Active connections: 316
Here's how it works:
- call `kwikemon thing status` to set the text for the monitor named "thing"
- call `kwikemon set thing status` to set the text for the monitor named "thing"
- fire up the server, `kwikemond`, that serves up these monitors in a big list or individually
Alternatively:
- continuously pipe data to `kwikemon <name of thing you are watching>` on stdin
- continuously pipe data to `kwikemon write <name of thing you are watching>` on stdin
- every time a full line of text is received on stdin it becomes the new status for <name of thing you are watching>
To see everything `kwikemon` can do run it without arguments.
To see everything `kwikemon` can do run `kwikemon help`.
# or with -h or --help
$ kwikemon
$ kwikemon help
This is very much a work in progress.
@ -44,12 +43,12 @@ You can use kwikemon as a library.
var kwikemon = require('kiwkemon');
kwikemon.set('foo', 'bar', function(err) {
kwikemon.fetch('foo', function(err, text) {
kwikemon.get('foo', function(err, text) {
console.log('foo = ' + text);
});
});
kwikemon.fetchAll(function(err, monitors) {
kwikemon.getAll(function(err, monitors) {
Object.keys(monitors).forEach(function (name) {
console.log(name + ' = ' + monitors[name]);
});
@ -70,28 +69,14 @@ A monitor named `nginx` stores its data in the hash `kwikemon:monitor:nginx`. Ha
are:
- text
- expire
- created
- modified
- updates
The list of all monitors is a set stored at `kwikemon:monitors`.
#### List
This is when you should clean out expired entries.
names = redis.smembers("kwikemon:monitors")
monitors = {}
for name in names:
if redis.exists("kwikemon:monitor:$name"):
monitors[name] = redis.hget("kwikemon:monitor:$name", "text")
return monitors
#### Read
redis.hgetall("kwikemon:monitor:nginx")
#### Update
#### Set
exists = redis.exists("kwikemon:monitor:nginx")
if exists:
@ -111,11 +96,48 @@ This is when you should clean out expired entries.
# optional
redis.expire("kwikemon:monitor:nginx", <ttl>)
#### Delete
#### Get
redis.hgetall("kwikemon:monitor:nginx")
#### Remove
redis.del("kwikemon:monitor:nginx")
redis.srem("kwikemon:monitors", "nginx")
#### Sweep
Clean out expired monitors. Call this before anything that relies on counting or iterating through all monitors.
for name in redis.smembers("kwikemon:monitors"):
if not redis.exists("kwikemon:monitor:$name"):
remove(name)
#### Count
Sweep before running a count.
sweep()
redis.scard("kwikemon:monitors")
#### List names
Sweep before listing.
sweep()
redis.smembers("kwikemon:monitors")
#### Get all
Sweep before geting all.
sweep()
monitors = {}
for name in list():
if redis.exists("kwikemon:monitor:$name"):
monitors[name] = get(name)
return monitors
## License

116
app.js Normal file
View file

@ -0,0 +1,116 @@
// Copyright 2013 Sami Samhuri
var express = require('express')
, Negotiator = require('negotiator')
, kwikemon = require('./kwikemon.js')
, app = module.exports = express()
, version = require('./version.js')
;
// Middleware
app.use(express.favicon('/dev/null'));
app.use(express.logger());
app.use(express.static(__dirname + '/public'));
// Views
app.set('view engine', 'jade');
app.set('views', __dirname + '/views');
// Routes
app.get('/', route('monitors', getMonitors));
app.get('/:name', route('monitor', getMonitor));
function route(template, buildContext) {
return function(req, res) {
buildContext(req, res, function(err, ctx) {
if (err) {
var message = err.message || String(err)
, status = message == 'not found' ? 404 : 500
;
res.format({
html: function() {
res.render('error', {
version: version,
pageTitle: 'Error',
err: err
});
},
text: function() {
res.send(renderText('error', { err: err }));
},
json: function() {
res.json({ message: message });
}
});
}
else {
ctx = ctx || {};
res.format({
html: function() {
ctx.version = version;
res.render(template, ctx);
},
text: function() {
res.send(renderText(template, ctx));
},
json: function() {
res.json(ctx);
}
});
}
});
};
}
// Rendering
function renderText(template, ctx) {
var text;
switch (template) {
case 'monitor':
text = String(ctx.monitor.text);
break;
case 'monitors':
text = Object.keys(ctx.monitors).sort().map(function(name) {
return name + ': ' + ctx.monitors[name].text;
}).join('\n');
break;
case 'error':
text = ctx.err.message || String(ctx.err);
break;
default:
throw new Error('unknown text template: ' + template);
}
return text;
}
//////////////////////
// Request handlers //
//////////////////////
function getMonitors(req, res, cb) {
kwikemon.getAll(function(err, monitors) {
cb(err, err ? null : {
pageTitle: 'Monitors',
monitors: monitors
});
});
}
function getMonitor(req, res, cb) {
var name = req.params.name;
kwikemon.get(name, function(err, mon) {
if (!mon) {
err = new Error('not found');
}
cb(err, err ? null : {
pageTitle: mon.name,
monitor: mon
});
});
}

View file

@ -2,120 +2,166 @@
var kwikemon = require('../kwikemon.js');
function usage() {
console.log("usage: kwikemon [options]");
console.log(" kwikemon <name> [text]");
console.log();
console.log("options:");
console.log(" -c, --clear remove all monitors");
console.log(" -f, --fetch <name> show the text of the named monitor");
console.log(" -n, --count count monitors");
console.log(" -h, --help show what you're reading now");
console.log(" -l, --list show all monitors");
console.log(" -r, --remove <name> remove the named monitor");
console.log(" -s, --sweep clean up expired & deleted monitors");
console.log(" -t, --ttl <name> show the TTL of the named monitor");
var commands = {};
function defineCommand(name, handler, args) {
commands[name] = { handler: handler, args: args };
}
defineCommand('clear', clear);
defineCommand('count', count);
defineCommand('get', get, 'name');
defineCommand('help', usage);
defineCommand('list', list);
defineCommand('remove', remove, 'name');
defineCommand('set', set, 'name text');
defineCommand('sweep', sweep);
defineCommand('ttl', ttl, 'name [ttl]');
defineCommand('write', write, 'name');
function main() {
var cmd = process.argv[2]
, handler = commands[cmd] && commands[cmd].handler
;
if (handler) {
handler.apply(null, process.argv.slice(3));
}
else {
if (cmd) {
console.log('error: unknown command ' + cmd);
}
usage();
process.exit(1);
}
}
var opt = process.argv[2]
, name = process.argv[2]
, text = process.argv[3]
;
if (opt && opt[0] == '-') {
switch (opt) {
case '-c':
case '--clear':
kwikemon.removeAll(function(err) {
process.exit(0);
});
break;
function clear() {
kwikemon.clear(function(err) {
process.exit(0);
});
}
case '-f':
case '--fetch':
var name = process.argv[3];
kwikemon.fetch(name, function(err, mon) {
function count() {
kwikemon.count(function(err, n) {
console.log(n);
process.exit(0);
});
}
function expire(name, ttl) {
kwikemon.ttl(name, ttl, function(err) {
if (err && err.message == 'not found') {
console.log('no monitor named', name);
process.exit(1);
}
else if (err) {
console.log('error: ' + (err.message || err));
process.exit(1);
}
else {
process.exit(0);
}
});
}
function get(name) {
if (name) {
kwikemon.get(name, function(err, mon) {
if (mon) {
console.log(mon.text);
process.exit(0);
}
else {
console.log('error: no monitor named', name);
console.log('no monitor named', name);
process.exit(1);
}
});
break;
}
else {
console.log('get requires a name');
process.exit(1);
}
}
case '-l':
case '--list':
kwikemon.fetchAll(function(err, monitors) {
Object.keys(monitors).forEach(function(name) {
console.log(name + ':', monitors[name].text);
});
function list() {
kwikemon.getAll(function(err, monitors) {
Object.keys(monitors).forEach(function(name) {
console.log(name + ':', monitors[name].text);
});
process.exit(0);
});
}
function remove(name) {
if (name) {
kwikemon.remove(name, function(err) {
process.exit(0);
})
}
else {
console.log('remove requires a name');
process.exit(1);
}
}
function set(name, text) {
if (name && text) {
kwikemon.set(name, text, function() {
process.exit(0);
});
break;
}
else {
console.log('set requires a name and some text')
process.exit(1);
}
}
case '-n':
case '--count':
kwikemon.count(function(err, n) {
console.log(n);
process.exit(0);
});
break;
function sweep() {
kwikemon.sweep(function(err) {
process.exit(0);
});
}
case '-r':
case '--remove':
name = process.argv[3];
if (name) {
kwikemon.remove(name, function(err) {
process.exit(0);
})
}
else {
console.log("error: --remove requires a name");
process.exit(1);
}
break;
case '-s':
case '--sweep':
kwikemon.sweep(function(err) {
process.exit(0);
});
break;
case '-t':
case '--ttl':
var name = process.argv[3];
kwikemon.fetchTTL(name, function(err, ttl) {
function ttl(name, ttl) {
if (ttl) return expire(name, Number(ttl));
if (name) {
kwikemon.ttl(name, function(err, ttl) {
if (typeof ttl == 'number') {
console.log(ttl);
process.exit(0);
}
else {
console.log('error: no monitor named', name);
console.log('no monitor named', name);
process.exit(1);
}
});
break;
default:
usage();
process.exit(name == '-h' || name == '--help' ? 0 : 1);
}
else {
console.log('ttl requires a name');
process.exit(1);
}
}
else if (name && text) {
kwikemon.set(name, text, function() {
process.exit(0);
function write(name) {
if (name) {
process.stdin.pipe(kwikemon.writer(name));
process.stdin.on('end', function() {
process.exit(0);
});
}
else {
console.log('write requires a name');
process.exit(1);
}
}
function usage() {
console.log('usage: kwikemon <command> [args...]');
console.log('commands:');
Object.keys(commands).sort().forEach(function(name) {
var args = commands[name].args || '';
console.log(' ' + name + ' ' + args);
});
}
else if (name) {
process.stdin.pipe(kwikemon.createWriter(name));
process.stdin.on('end', function() {
process.exit(0);
});
}
else {
usage();
process.exit(1);
}
if (require.main == module) {
main();
}

View file

@ -1,8 +1,9 @@
#!/usr/bin/env node
var server = require('../server.js')
, port = process.argv[2]
, host = process.argv[3]
var port = process.argv[2] || 1111
, host = process.argv[3] || '127.0.0.1'
, app = require('../app.js')
;
server.start(port, host);
app.listen(port, host);
console.log('kwikemond listening on ' + host + ':' + port);

View file

@ -8,29 +8,47 @@ module.exports = {
// read
, exists: callbackOptional(exists)
, fetch: callbackOptional(fetch)
, fetchTTL: callbackOptional(fetchTTL)
, list: list
, fetchAll: fetchAll
, get: callbackOptional(get)
, ttl: callbackOptional(ttl)
, count: count
, list: list
, getAll: getAll
// remove
, remove: callbackOptional(remove)
, removeAll: removeAll
, clear: clear
, sweep: sweep
// change redis client
, redis: setRedis
, redis: redis
};
var async = require('async')
, redis = require('redis').createClient()
, fs = require('fs')
, Redis = require('redis')
, redisClient
, toml = require('toml')
, LineEmitter = require('./line_emitter.js')
;
function setRedis(newRedis) {
if (redis) redis.end();
redis = newRedis;
function redis(newRedis) {
if (newRedis){
if (redisClient) redisClient.end();
redisClient = newRedis;
}
else {
if (!redisClient) {
var configFile = process.env.HOME + '/.kwikemon.toml'
, config = {}
;
if (fs.existsSync(configFile)) {
config = toml.parse(fs.readFileSync(configFile));
}
config.redis = config.redis || {};
redisClient = Redis.createClient(config.redis.port, config.redis.host, config.redis.options);
}
return redisClient;
}
}
// Make the callback argument of a function optional.
@ -39,15 +57,15 @@ function setRedis(newRedis) {
// that accepts the callback is returned, with the
// rest of the arguments fixed (like bind).
//
// function fetch(id, cb) { db.fetch(id, cb); }
// fetch = callbackOptional(fetch);
// function get(id, cb) { db.get(id, cb); }
// get = callbackOptional(get);
//
// function print(err, x) { if (err) throw err; console.log(x); }
//
// fetch(1, print);
// get(1, print);
//
// var fetch1 = fetch(1);
// fetch1(print);
// var get1 = get(1);
// get1(print);
function callbackOptional(fn, ctx) {
return function() {
var args = Array.prototype.slice.call(arguments);
@ -69,7 +87,7 @@ function k(name) {
}
function exists(name, cb) {
redis.exists(k(name), function(err, exists) {
redis().exists(k(name), function(err, exists) {
if (err) return cb(err);
cb(null, exists == 1);
});
@ -88,10 +106,11 @@ function set(name, text, options, cb) {
;
exists(name, function(err, exists) {
var fields = {
text: text
name: name
, text: text
, modified: Date.now()
}
, multi = redis.multi()
, multi = redis().multi()
;
if (!exists) {
fields.created = Date.now();
@ -118,16 +137,37 @@ function writer(name) {
return le;
}
function fetch(name, cb) {
redis.hgetall(k(name), cb);
function get(name, cb) {
redis().hgetall(k(name), cb);
}
function fetchTTL(name, cb) {
redis.ttl(k(name), cb);
function expire(name, ttl, cb) {
exists(name, function(err, exists) {
if (err || !exists) {
return cb(err || new Error('not found'));
}
redis().multi()
.hset(k(name), 'expire', ttl)
.expire(k(name), ttl)
.exec(cb);
});
}
function ttl(name, ttl, cb) {
if (typeof ttl == 'number') {
expire(name, ttl, cb);
}
else {
cb = ttl;
redis().ttl(k(name), cb);
}
}
function count(cb) {
redis.scard('kwikemon:monitors', cb);
sweep(function(err) {
if (err) return cb(err);
redis().scard('kwikemon:monitors', cb);
})
}
function sweep(cb) {
@ -138,7 +178,7 @@ function sweep(cb) {
if (i == n) cb();
}
;
redis.smembers('kwikemon:monitors', function(err, names) {
redis().smembers('kwikemon:monitors', function(err, names) {
if (err) return cb(err);
n = names.length;
if (n == 0) return cb();
@ -160,24 +200,24 @@ function sweep(cb) {
function list(cb) {
sweep(function(err) {
if (err) return cb(err);
redis.smembers('kwikemon:monitors', cb);
redis().smembers('kwikemon:monitors', cb);
});
}
function fetchAll(cb) {
function getAll(cb) {
var monitors = {};
list(function(err, names) {
if (err) return cb(err);
var fetchers = names.sort().map(function(name) {
var geters = names.sort().map(function(name) {
return function(done) {
fetch(name, function(err, text) {
get(name, function(err, text) {
if (err) return done(err);
monitors[name] = text;
done();
});
};
});
async.parallel(fetchers, function(err, _) {
async.parallel(geters, function(err, _) {
if (err) return cb(err);
cb(null, monitors)
});
@ -185,16 +225,16 @@ function fetchAll(cb) {
}
function remove(name, cb) {
redis.multi()
redis().multi()
.del(k(name))
.srem('kwikemon:monitors', name)
.exec(cb);
}
function removeAll(cb) {
redis.smembers('kwikemon:monitors', function(err, names) {
function clear(cb) {
redis().smembers('kwikemon:monitors', function(err, names) {
if (err) return cb(err);
var multi = redis.multi();
var multi = redis().multi();
names.forEach(function(name) {
multi.del(k(name));
multi.srem('kwikemon:monitors', name);

View file

@ -1,5 +1,5 @@
{ "name": "kwikemon"
, "version": "0.0.2"
, "version": "0.0.6"
, "description": "monitor one-off things on your servers"
, "author": "Sami Samhuri <sami@samhuri.net>"
, "license": "MIT"
@ -11,9 +11,12 @@
, "kwikemond": "./bin/kwikemond"
}
, "dependencies": {
"paramify": "0.0.x"
"async": "0.2.x"
, "express": "3.0.x"
, "jade": "0.31.x"
, "negotiator": "0.2.x"
, "redis": "0.8.x"
, "async": "0.2.x"
, "toml": "0.4.x"
}
, "devDependencies": {
"mocha": "1.10.x"
@ -22,4 +25,4 @@
, "engines": {
"node": ">=0.10"
}
}
}

15
public/css/style.css Normal file
View file

@ -0,0 +1,15 @@
ul#monitors {
list-style: none;
}
li.monitor {
}
#footer {
width: 80%;
margin: 0.5em auto;
padding: 0.3em 1em;
text-align: center;
border-top: solid 1px #aaa;
}

5
ruby/Gemfile Normal file
View file

@ -0,0 +1,5 @@
source 'https://rubygems.org'
gem 'hashie', '~> 2.0.5'
gem 'redis', '~> 3.0.4'
gem 'toml', '~> 0.0.4'

18
ruby/Gemfile.lock Normal file
View file

@ -0,0 +1,18 @@
GEM
remote: https://rubygems.org/
specs:
blankslate (2.1.2.4)
hashie (2.0.5)
parslet (1.5.0)
blankslate (~> 2.0)
redis (3.0.4)
toml (0.0.4)
parslet
PLATFORMS
ruby
DEPENDENCIES
hashie (~> 2.0.5)
redis (~> 3.0.4)
toml (~> 0.0.4)

17
ruby/kwikemon.gemspec Normal file
View file

@ -0,0 +1,17 @@
$LOAD_PATH << File.expand_path('../lib', __FILE__)
require 'kwikemon/version'
Gem::Specification.new do |s|
s.name = 'kwikemon'
s.version = Kwikemon::VERSION
s.license = 'MIT'
s.summary = 'Ruby client for kwikemon.'
s.description = 'Read & write simple monitors using Redis.'
s.author = 'Sami Samhuri'
s.email = 'sami@samhuri.net'
s.homepage = 'https://github.com/samsonjs/kwikemon'
s.require_path = './lib'
s.files = ['lib/kwikemon.rb', 'lib/kwikemon/monitor.rb', 'lib/kwikemon/version.rb']
s.add_dependency 'redis', '~> 3.0.4'
s.required_ruby_version = '>= 1.9.1'
end

139
ruby/lib/kwikemon.rb Normal file
View file

@ -0,0 +1,139 @@
# Copyright 2013 Sami Samhuri <sami@samhuri.net>
#
# MIT License
# http://sjs.mit-license.org
lib_dir = File.expand_path('../', __FILE__)
$LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
require 'hashie'
require 'redis'
require 'toml'
require 'kwikemon/monitor'
require 'kwikemon/version'
module Kwikemon
extend self
include Enumerable
def redis
@redis ||= Redis.new(config.redis || {})
end
def redis=(redis)
@redis = redis
end
def key_prefix
@key_prefix ||= "kwikemon"
end
def key_prefix=(key_prefix)
@key_prefix = key_prefix
end
def key(x)
"#{key_prefix}:#{x}"
end
Monitor.on(:create) do |name|
redis.sadd(key('monitors'), name)
end
Monitor.on(:remove) do |name|
redis.srem(key('monitors'), name)
end
# Set `name` to `value`.
#
# @param name [#to_s] name of the monitor
# @param text [#to_s] status text
def set(name, text)
Monitor.new(name, text).save
end
# Check if `name` exists3
#
# @param name [#to_s] name of the monitor
# @return [true, false] true if monitor exists, otherwise false
def exists?(name)
Monitor.new(name).exists?
end
# Get the value of `name`. Returns `nil` if it doesn't exist.
#
# @param name [#_tos] name of the monitor
# @return [String, nil] status text, or `nil` if it doesn't exist
def get(name)
Monitor.new(name).text
end
# Get the TTL in seconds of `name`. Returns `nil` if it doesn't exit.
#
# @param name [#_tos] name of the monitor
# @return [String, nil] TTL, or `nil` if it doesn't exist
def ttl(name)
Monitor.new(name).ttl
end
# Count all monitors.
def count
redis.scard(key('monitors'))
end
# List all monitor names.
def list
redis.smembers(key('monitors'))
end
def each
list.each { |m| yield(m) }
end
# Get a `Hash` of all monitors.
def get_all
list.inject({}) do |ms, name|
ms[name] = Monitor.new(name).text
ms
end
end
# Remove the monitor named `name`.
def remove(name)
Monitor.new(name).remove
end
# Clear all monitors.
def clear
list.each do |name|
remove(name)
end
end
# Clean up expired monitors.
def sweep
list.each do |name|
remove(name) unless exists?(name)
end
end
private
def config
@config ||= Hashie::Mash.new(load_config)
end
def load_config
path = File.join(ENV['HOME'], '.kwikemon.toml')
if File.exists?(path)
TOML.load_file(path)
else
{}
end
end
end

View file

@ -0,0 +1,104 @@
# Copyright 2013 Sami Samhuri <sami@samhuri.net>
#
# MIT License
# http://sjs.mit-license.org
module Kwikemon
class Monitor
DefaultTTL = 86400 # 1 day
attr_accessor :redis
attr_reader :name, :text, :ttl, :created, :modified
@listeners = Hash.new { |h, k| h[k] = [] }
def Monitor.on(event, &block)
@listeners[event] << block
end
def Monitor.emit(event, *args)
@listeners[event].each { |handler| handler.call(*args) }
end
def initialize(name, text = nil)
@name = name
@text = text
end
def save
if exists?
update(text)
else
create
end
end
def exists?
redis.exists(key)
end
def create
raise MonitorError.new('name cannot be blank') if name.to_s.strip.length == 0
redis.hmset(key, *to_a)
self.class.emit(:create, name)
self
end
def update(text, ttl = nil)
raise MonitorError.new('name cannot be blank') if name.to_s.strip.length == 0
redis.hmset(key, 'text', text, 'modified', Time.now.to_i)
redis.ttl(key, ttl) if ttl
self
end
def remove
redis.del(key)
self.class.emit(:remove, name)
self
end
def key
Kwikemon.key("monitor:#{name}")
end
def ttl
@ttl ||= exists? ? redis.ttl(key) : nil
end
def created
@created ||= exists? ? redis.hget(key, 'created').to_i : nil
end
def modified
@modified ||= exists? ? redis.hget(key, 'modified').to_i : nil
end
def text
@text ||= exists? ? redis.hget(key, 'text') : nil
end
private
def redis
Kwikemon.redis
end
def to_hash
{ name: name,
text: text,
ttl: ttl || DefaultTTL,
created: created || Time.now.to_i,
modified: modified || Time.now.to_i
}
end
def to_a
to_hash.to_a
end
end
end

View file

@ -0,0 +1,3 @@
module Kwikemon
VERSION = '0.0.9'
end

View file

@ -1,65 +0,0 @@
// 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(/^\//, '')
, type = 'html'
, m
;
if (name == 'favicon.ico') return res.end();
if (m = name.match(/\.(json|txt)$/)) {
type = m[1];
name = name.replace(RegExp('\.' + type + '$'), '');
}
if (name) {
kwikemon.fetch(name, function(err, text) {
if (err) {
res.end('error: ' + (err.message || 'unknown'));
return;
}
res.end(text);
});
}
// all
else {
kwikemon.fetchAll(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();
});
}
}

53
test.js
View file

@ -12,14 +12,14 @@ before(function(done) {
done();
});
});
beforeEach(kwikemon.removeAll);
after(kwikemon.removeAll);
beforeEach(kwikemon.clear);
after(kwikemon.clear);
describe("kwikemon", function() {
describe("#set", function() {
it("should set text", function(done) {
kwikemon.set('foo', 'bar', function(err) {
kwikemon.fetch('foo', function(err, mon) {
kwikemon.get('foo', function(err, mon) {
assert(mon.text == 'bar');
done();
});
@ -28,7 +28,7 @@ describe("kwikemon", function() {
it("should overwrite text", function(done) {
kwikemon.set('foo', 'baz', function(err) {
kwikemon.fetch('foo', function(err, mon) {
kwikemon.get('foo', function(err, mon) {
assert(mon.text == 'baz');
done();
});
@ -37,7 +37,7 @@ describe("kwikemon", function() {
it("should set custom ttls", function(done) {
kwikemon.set('foo', 'bar', { ttl: 1 }, function(err) {
kwikemon.fetchTTL('foo', function(err, ttl) {
kwikemon.ttl('foo', function(err, ttl) {
assert(ttl <= 1);
done();
});
@ -46,7 +46,7 @@ describe("kwikemon", function() {
it("should not expire with a ttl of zero", function(done) {
kwikemon.set('foo', 'bar', { ttl: 0 }, function(err) {
kwikemon.fetchTTL('foo', function(err, ttl) {
kwikemon.ttl('foo', function(err, ttl) {
assert(ttl == -1);
done();
});
@ -55,7 +55,7 @@ describe("kwikemon", function() {
it("should not expire when ttl is < 0", function(done) {
kwikemon.set('foo', 'bar', { ttl: -1 }, function(err) {
kwikemon.fetchTTL('foo', function(err, ttl) {
kwikemon.ttl('foo', function(err, ttl) {
assert(ttl == -1);
done();
});
@ -92,12 +92,12 @@ describe("kwikemon", function() {
});
});
describe("#fetch", function() {
it("should fetch the last text monitored", function(done) {
describe("#get", function() {
it("should get the last text monitored", function(done) {
async.series([
kwikemon.set('foo', 'bar')
, kwikemon.set('foo', 'marcellus')
, kwikemon.fetch('foo')
, kwikemon.get('foo')
],
function(err, results) {
var mon = results[2];
@ -107,18 +107,18 @@ describe("kwikemon", function() {
);
});
it("should fetch null for non-existent monitors", function(done) {
kwikemon.fetch('non-existent', function(err, mon) {
it("should get null for non-existent monitors", function(done) {
kwikemon.get('non-existent', function(err, mon) {
assert(mon == null);
done();
});
});
});
describe("#fetchTTL", function() {
it("should fetch the last TTL set", function(done) {
describe("#ttl", function() {
it("should get the last TTL set", function(done) {
kwikemon.set('foo', 'bar', { ttl: 300 }, function(err) {
kwikemon.fetchTTL('foo', function(err, ttl) {
kwikemon.ttl('foo', function(err, ttl) {
assert(ttl <= 300);
done();
});
@ -126,20 +126,31 @@ describe("kwikemon", function() {
});
it("should return -1 for non-existent monitors", function(done) {
kwikemon.fetchTTL('non-existent', function(err, ttl) {
kwikemon.ttl('non-existent', function(err, ttl) {
assert(ttl == -1);
done();
});
});
it("should set a ttl if given one", function(done) {
kwikemon.set('foo', 'bar', function(err) {
kwikemon.ttl('foo', 100, function(err) {
kwikemon.ttl('foo', function(err, ttl) {
assert(ttl <= 100);
done();
});
});
});
});
});
describe("#fetchAll", function() {
it("should fetch all monitors", function(done) {
describe("#getAll", function() {
it("should get all monitors", function(done) {
async.series([
kwikemon.set('a', '1')
, kwikemon.set('b', '2')
, kwikemon.set('c', '3')
, kwikemon.fetchAll
, kwikemon.getAll
],
function(err, results) {
var monitors = results.pop()
@ -187,12 +198,12 @@ describe("kwikemon", function() {
});
});
describe("#removeAll", function() {
describe("#clear", function() {
it("should remove the named monitor", function(done) {
async.series([
kwikemon.set('foo', 'bar')
, kwikemon.set('baz', 'quux')
, kwikemon.removeAll
, kwikemon.clear
, kwikemon.exists('foo')
, kwikemon.exists('baz')
, kwikemon.count

3
version.js Normal file
View file

@ -0,0 +1,3 @@
// Copyright 2013 Sami Samhuri
module.exports = JSON.parse(require('fs').readFileSync(__dirname + '/package.json')).version;

4
views/error.jade Normal file
View file

@ -0,0 +1,4 @@
extends layout
block content
p.
error: #{err.message || err}

13
views/layout.jade Normal file
View file

@ -0,0 +1,13 @@
doctype 5
html(lang="en")
head
title kwikemon
link(rel="stylesheet", href="/css/style.css")
body
block nav
block title
h1= pageTitle || 'kwikemon'
#content
block content
#footer
a(href="https://github.com/samsonjs/kwikemon") kwikemon #{version}

5
views/monitor.jade Normal file
View file

@ -0,0 +1,5 @@
extends layout
block nav
p: a(href="/") &larr; monitors
block content
p= monitor.text

5
views/monitors.jade Normal file
View file

@ -0,0 +1,5 @@
extends layout
block content
ul#monitors
- each monitor in monitors
li.monitor: a(href="/#{monitor.name}") #{monitor.name}: #{monitor.text}