Skip to content

Commit

Permalink
Merge branch 'feature/dashboard' of github.com:jeffsu/upbeat into fea…
Browse files Browse the repository at this point in the history
…ture/dashboard
  • Loading branch information
vipworld committed Sep 11, 2012
2 parents 2029d5a + 6291185 commit facdd1a
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 58 deletions.
108 changes: 107 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,107 @@
# Upbeat is a high performance healthcheck/dashboard based in node
# Upbeat

Upbeat is a high performance node-based healthcheck/dashboard. Upbeat allows you to run health checks and provides a dashboard to chart the performance. It also allows you to proxy and cache these health checks so they don't tax your system. You can now reduces the number of health checks to a service from O(N) to O(1).

## Installation

As executable
```bash
npm install -g upbeat
```

Run upbeat
```bash
upbeat config.yml
```

As library
```bash
npm install upbeat
```

```js
var upbeat = require('upbeat');
var server = new upbeat.Server(configObject);
server.run();
```

## Configuration

Quickstart config:

```yml
dashboard:
port: 2468

sync:
redis:
port: 6379
host: localhost

services:
google:
www:
strategy: http
url: https://www.google.com
connection:
strategy: tcp
host: google.com
port: 80
```
### Services
<<<<<<< Updated upstream
Services are a way of grouping several sensor checks together. In the example above, we have a "google" service w
hich we check by making a get request to "https://www.google.com" and also seeing if we have a connection to port 8
0 on the "google.com" host. In the yaml config, a service is a "hash" of sensors where the keys are the names of
the sensors and the values are the configuration.
### Sensors
Sensors are a way of describing a health check. Each sensor config MUST at least have a strategy. Common configuration
options accross all strategies are:
* timeout: number (milliseconds) to define how long it will allow a check before declaring it a failure
* interval: number (milliseconds) of time to wait between health checks (called after the result of a check)
* fall: number of fails to be considered down
* rise: number of passes to be considered up
Here are some
examples of how you can use sensors and their strategies:
*tcp*
```yml
strategy: tcp
host: google.com
port: 80
```
*http*
```yml
strategy: http
url: https://www.google.com
```
*pidfile*
```yml
strategy: pidfile
pidfile: /var/pids/mysql.pid
```
*redis*
```yml
strategy: redis
host: localhost # defaults to localhost
port: 6379 # defaults to 6379
command: [ 'exists', 'foo' ]
```
*mysql*
```yml
strategy: mysql
host: host # required
user: user # required
password: pass # required
sql: "SELECT * FROM users LIMIT 1" # defaults to "SHOW TABLES"
```
30 changes: 24 additions & 6 deletions lib/sensor.ms
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,20 @@ export class Sensor {

this.setEvents();

this.lastChange = null;
this.lastChange = (new Date).getTime();
var firstChange = true;

this.on('change', #(isHealthy) {
var now = (new Date).getTime();

if (!self.lastChange)
this[isHealthy ? 'uptime' : 'downtime'] += self.lastChange - now;

self.lastChange = now;
var delta = now - self.lastChange;

if (firstChange) {
self[isHealthy ? 'upTime' : 'downTime'] += delta;
firstChange = false;
} else {
self[isHealthy ? 'downTime' : 'upTime'] += delta;
self.lastChange = now;
}
});
}

Expand All @@ -77,6 +83,17 @@ export class Sensor {
return this.stats.getData(time);
}

function getUpTime() {
return this.upTime +
(this.isHealthy ? (new Date).getTime()-this.lastChange : 0);
}

function getDownTime() {
return this.downTime +
(!this.isHealthy ? (new Date).getTime()-this.lastChange : 0);
}


function setEvents() {
this.on('fail', #(time) {
self.lastFailure = (new Date);
Expand Down Expand Up @@ -134,6 +151,7 @@ export class Sensor {
}

else {
this.lastError = err;
this.failCount++;
this.lastFail = now;
}
Expand Down
34 changes: 19 additions & 15 deletions lib/server.ms
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,16 @@ export class Server {
}

function sync(redis, time) {
if (this.syncConfig.sensors) {
for (var serviceName in this.services) {
var service = this.services[serviceName];
for (var sensorName in service.sensors) {
var sensor = service.sensors[sensorName];
sensor.sync(time, redis, "upbeat:services:" + serviceName + ":" + sensorName);
}
for (var serviceName in this.services) {
var service = this.services[serviceName];
for (var sensorName in service.sensors) {
var sensor = service.sensors[sensorName];
sensor.sync(time, redis, "upbeat:services:" + serviceName + ":" + sensorName);
}
}

if (this.syncConfig.stats) {
for (var k in this.stats) {
this.stats[k].syncTime(time, redis, k);
}
for (var k in this.stats) {
this.stats[k].syncTime(time, redis, k);
}
}

Expand All @@ -59,6 +55,7 @@ export class Server {
this.services = {};
this.stats = {};
this.toRun = [];
this.logger = require('winston');

this.buildServices(config.services);
this.buildStats(config.stats);
Expand All @@ -67,6 +64,8 @@ export class Server {
if (self.trackedEvents[type]) self.log(serviceName, sensorName, type, sensor, data1, data2);
});

this.setupLogging(config.log);

if (config.dashboard)
this.buildDash(config.dashboard);

Expand Down Expand Up @@ -125,11 +124,12 @@ export class Server {
}

function addEvents(sensor, eventName, serviceName, sensorName) {
var args = arguments;
sensor.on(eventName, #{ self.emit('sensor', serviceName, sensorName, eventName, sensor, $1, $2) });
}

function log(serviceName, sensorName, eventType, sensor, data1, data2) {
console.log(serviceName + '/' + sensorName + ': ' + eventType + ' - ' + (new Date).toString());
this.logger.info(serviceName + '/' + sensorName + ': ' + eventType + ' - ' + (new Date).toString());
}

function buildStats(config) {
Expand All @@ -139,11 +139,15 @@ export class Server {
}
}

function setupLogging(logs) {
this.trackedEvents = {};
if (!logs) return;
foreach (var log in logs)
this.trackedEvents[log] = true;
}

function buildServices(config) {
if (!config) return;
this.trackedEvents = {};
if (config.log)
config.log.forEach(#{ self.trackedEvents[$1] = true });

for (var k in config)
this.services[k] = this.buildService(k, config[k]);
Expand Down
33 changes: 10 additions & 23 deletions lib/strategies/http.ms
Original file line number Diff line number Diff line change
@@ -1,30 +1,17 @@
var http = require('http');
var URL = require('url');
var request = require('request');
var URL = require('url');

module.exports = #(options) {
var method = options.method || 'GET';
var url = URL.parse(options.url);

var reqOptions = {
host: url.hostname,
method: method,
port: url.port,
path: url.path
};
var newOpts = {};
newOpts.__proto__ = options;
newOpts.url = URL.parse(options.url);

return #(cb) {
var req = http.request(reqOptions);

req.on('response', #(res) {
var code = res.statusCode;
var data = "";

res.on('data', #(chunk) { data += chunk.toString(); });
res.on('end', #{ cb(code != 200, data) });
res.on('close', #(err) { cb(err) });
var req = request(newOpts, #(e, r, body) {
if (e) return cb(e);
var code = r.statusCode;
if (parseInt(code) >= 400) return cb('error status code ' + code);
cb();
});

req.on('error', #(err) { cb(err) });
req.end();
};
};
7 changes: 4 additions & 3 deletions lib/strategies/mysql.ms
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
module.exports = #(options) {
var sql = options.query || "SHOW TABLES";
var mysql = require('mysql');
var conn = mysql.createConnection({ host: options.host, user: option.user, password: options.password });
conn.connect();
var conn = mysql.createClient(options);

return #(cb) {
conn.query(sql, #(err, rows, fields) { cb(err); });
conn.query(sql, #(err, rows, fields) {
cb(err);
});
};
};
18 changes: 16 additions & 2 deletions mock/example.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
services:
# mysql:
# mysql:
# strategy: mysql
# sql: SHOW TABLES
# user: root
# password: root
# database: mysql

google:
www:
strategy: http
url: https://www.google.com
connection:
strategy: tcp
host: google.com
port: 80
fast-www:
good:
strategy: http
Expand Down Expand Up @@ -57,8 +73,6 @@ sync:
day: 60000
week: 60000

stats: true
sensors: true
redis:
port: 6379
host: localhost
File renamed without changes.
26 changes: 18 additions & 8 deletions www/views/sensor.jade
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ block breadcrumbs
block content
table.table.table-bordered.table-condensed
tr
th Healthy
th Healthy?
td= affirm(sensor.isHealthy)
th Slow
th Slow?
td= affirm(sensor.isSlow)
tr
th Pass Count
Expand All @@ -20,13 +20,23 @@ block content
th Status
td= sensor.status
th Last Failure
td= sensor.lastFailure
td
= sensor.lastFailure
if sensor.lastError
= " (" + sensor.lastError.toString() + ")";
tr
th(colspan=4) Options
each val, name in sensor.options
tr
th= name
td(colspan=3)= val
th Uptime
td
| Total: #{sensor.getUpTime()}ms
if sensor.isHealthy
| since #{(new Date).getTime() - sensor.lastChange}ms ago
th Downtime
td
| Total: #{sensor.getDownTime()}ms
unless sensor.isHealthy
| since #{(new Date).getTime() - sensor.lastChange}ms ago
tr
td(colspan=4)= "Options: " + JSON.stringify(sensor.options);

include _time-nav
table(style="width: 100%")
Expand Down

0 comments on commit facdd1a

Please sign in to comment.