thalassa-aqueduct
Version:
Dynamic haproxy load balancer and configuration. Part of Thalassa
116 lines (96 loc) • 3.52 kB
JavaScript
var level = require('level')
, assert = require('assert')
, util = require('util')
, path = require('path')
, mkdirp = require('mkdirp')
;
//
// opts:
// - dbPath
// - secondsToRetainStats
//
var Db = module.exports = function Db (opts, cb) {
var self = this;
if (typeof opts !== 'object') opts = {};
this.log = (typeof opts.log === 'function') ? opts.log : function (){};
assert(opts.dbPath, 'Db.js: opts.dbPath, dbPath to leveldb database, must be passed!');
var dbPath = self.DBPATH = opts.dbPath;
self.SECONDS_TO_RETAIN_STATS = opts.secondsToRetainStats || 300;
mkdirp(dbPath, function (err) {
if (err) {
self.log('error', 'mkdirp ' + dbPath, String(err));
throw err;
}
var statsDbPath = path.join(dbPath, 'statsDb');
self.statsDb = level(statsDbPath, { valueEncoding : 'json' });
self.log('debug', 'statsDbPath=' + statsDbPath);
var activityDbPath = path.join(dbPath, 'activityDb');
self.activityDb = level(activityDbPath, { valueEncoding : 'json' });
self.log('debug', 'activityDbPath=' + activityDbPath);
if (typeof cb === 'function') cb();
});
};
Db.prototype.writeStat = function(statObj) {
var key = [statObj.hostId, statObj.id, statObj.time].join('~');
this.statsDb.put(key, statObj);
this.trimStats();
};
Db.prototype.writeActivity = function(activityObj) {
this.log('debug', "activity", activityObj);
var key = [activityObj.time, activityObj.object].join('~');
this.activityDb.put(key, activityObj);
};
Db.prototype.trimStats = function () {
var self = this;
// if we're already trimming, return
if (self.isTrimming) return;
self.isTrimming = true;
// self.log('debug', 'trimStats starting');
var ws = self.statsDb.createWriteStream();
var numKeysDeleted = 0;
var numKeysConsidered = 0;
var startTime = Date.now();
var timeToExpire = Date.now() - (self.SECONDS_TO_RETAIN_STATS * 1000);
var rs = self.statsDb.createReadStream({ keys: true, values: false })
.on('data', function (key) {
numKeysConsidered++;
var parts = key.split('~');
var epoch = parseInt(parts[2], 10) || 0; // if the key doesn't contain the time, aggressively delete it
if (epoch < timeToExpire) {
//self.log('debug', 'trimStats deleting (' + (epoch - timeToExpire) + ') ' + key);
ws.write({ type: 'del', key: key });
numKeysDeleted++;
}
})
.on('end', function () {
ws.end();
var duration = Date.now()-startTime;
self.log('debug', util.format('trimStats trimmed %s of %s in %sms (%s)', numKeysDeleted, numKeysConsidered, duration, (numKeysConsidered/duration)));
self.allowTrimmingIn(6000);
})
.on('error', function (err) {
self.log('error', 'trimStats reading keystream from statsDb', String(err));
ws.end();
});
ws.on('error', function (err) {
self.log('error', 'trimStats write stream to statsDb', String(err));
rs.destroy();
});
};
Db.prototype.statsValueStream = function(hostId) {
var opts = (hostId) ? { start: hostId + '~', end: hostId + '~~' } : undefined;
return this.statsDb.createValueStream(opts);
};
Db.prototype.activityValueStream = function(opts) {
if (!opts) opts = {};
if (!opts.start) opts.start = Date.now();
if (!opts.limit) opts.limit = 50;
opts.reverse = true;
return this.activityDb.createValueStream(opts);
};
Db.prototype.allowTrimmingIn = function (t) {
var self = this;
setTimeout(function () {
self.isTrimming = false;
}, t);
};