UNPKG

keuss

Version:

Enterprise-grade Job Queues for node.js backed by redis, MongoDB or PostgreSQL

290 lines (227 loc) 6.3 kB
var _ = require ('lodash'); var async = require ('async'); var Stats = require ('../Stats'); var RedisConn = require('../utils/RedisConn'); var debug = require('debug')('keuss:Stats:Redis'); /* * redis using HINCRBY * * stores in: * - counters in keuss:stats:<ns>:<name>:counter_<counter> -> int * - opts (queue creation opts) in keuss:stats:<ns>:<name>:opts -> string/json */ class RedisStats extends Stats { constructor(ns, name, factory, opts) { super (ns, name, factory); this._id = 'keuss:stats:' + ns + ':' + name; this._opts = opts || {}; this._rediscl = factory._rediscl; this._cache = {}; this._rediscl.hset (this._id, 'name', this._name); this._rediscl.hset (this._id, 'ns', this._ns); debug ('created redis stats with ns %s, name %s, options %j', ns, name, opts); } values(cb) { this._rediscl.hgetall (this._id, (err, v) => { if (err) return cb(err); // convert values to numeric var ret = {}; for (let k in v) { // filter counters if (k.startsWith ('counter_')) _.set (ret, k.substring (8), parseInt(v[k])); } cb (null, ret); }); } _raw_values (cb) { this._rediscl.hgetall (this._id, (err, v) => { if (err) return cb(err); var ret = {}; for (let k in v) { if (k.startsWith ('counter_')) ret[k.substring (8)] = v[k]; } cb (null, ret); }); } paused (val, cb) { if (!cb) { // get, val is cb cb = val; val = undefined; this._rediscl.hget (this._id, 'paused', (err, res) => { if (err) return cb(err); if (!res) return cb(null, false); return cb (null, (res == 'true' ? true : false)); }); } else { // set this._rediscl.hset (this._id, 'paused', val ? 'true' : 'false', err => cb (err)); } } _flush (cb) { _.forEach (this._cache, (value, key) => { if (value) { this._rediscl.hincrby (this._id, 'counter_' + key, value); debug ('stats-redis[%s]: flushed %d -> %s', this._id, value, 'counter_' + key); this._cache[key] = 0; } }); if (cb ) setImmediate (() => cb ()); } _ensureFlush() { if (this._flusher) return; this._flusher = setTimeout(() => { this._flusher = undefined; this._flush (); }, this._opts.flush_period || 100); } _cancelFlush() { if (this._flusher) { clearTimeout(this._flusher); this._flusher = undefined; } } incr(v, delta, cb) { if ((delta === null) || (delta === undefined)) delta = 1; if (!this._cache[v]) { this._cache[v] = 0; } this._cache[v] += delta; this._ensureFlush(); if (cb) cb(); } decr(v, delta, cb) { if ((delta === null) || (delta === undefined)) delta = 1; this.incr(v, -delta, cb); } opts (opts, cb) { if (!cb) { // get cb = opts; this._rediscl.hget (this._id, 'opts', (err, res) => { if (err) return cb(err); if (!res) return cb(null, {}); try { if (res) res = JSON.parse(res); cb (null, res); } catch (e) { cb(e); } }); } else { // set this._rediscl.hset (this._id, 'opts', JSON.stringify (opts || {}), cb); } } clear(cb) { this._cancelFlush(); this._cache = {}; var tasks = [ cb => this._rediscl.hdel (this._id, 'opts', cb) ]; this._raw_values ((err, vals) => { _.forEach (vals, (v, k) => { tasks.push (cb => this._rediscl.hdel (this._id, 'counter_' + k, cb)); }); async.series (tasks, err => { if (cb) cb(err); }); }); } close (cb) { this._cancelFlush(); this._flush (cb); } } class RedisStatsFactory { constructor(opts) { this._opts = opts || {}; this._rediscl = RedisConn.conn(this._opts); this._instances = {}; debug ('created redis stats factory with option %j', opts); } static Type() { return 'redis' } type() { return RedisStatsFactory.Type() } stats(ns, name, opts) { var k = name + '@' + ns; if (!this._instances [k]) { this._instances [k] = new RedisStats (ns, name, this, opts); debug ('created redis stats with ns %s, name %s, opts %j', ns, name, opts); } return this._instances [k]; } queues (ns, opts, cb) { if (!cb) { cb = opts; opts = {}; } this._rediscl.keys('keuss:stats:' + ns + ':?*', (err, queues) => { if (err) return cb(err); if (opts.full) { var tasks = {}; queues.forEach(q => { tasks[q.substring(13 + ns.length)] = cb => { this._rediscl.hgetall (q, (err, v) => { if (err) return cb(err); var ret = {counters: {}}; for (let k in v) { if (k.startsWith ('counter_')) { ret.counters[k.substr (8)] = parseInt(v[k]); } else if (k == 'opts') { ret[k] = JSON.parse (v[k]); } else if (k == 'paused') { ret[k] = (v[k] == 'true' ? true : false); } else { ret[k] = v[k]; } } if (ret.paused === undefined) ret.paused = false; cb (null, ret); }); }; }); async.parallel (tasks, cb); } else { var ret = []; queues.forEach(q => { var qname = q.substring(13 + ns.length); ret.push(qname); }); cb(null, ret); } }); } close (cb) { var tasks = []; // flush pending stats _.each (this._instances, (v, k) => { tasks.push ((cb) => { debug (`closing RedisStats ${k}`); v.close (cb); }); }); async.series ([ (cb) => async.parallel (tasks, cb), (cb) => { debug (`closing RedisStatsFactory redis conn`); this._rediscl.quit(cb); } ], cb); } } function creator (opts, cb) { if (!cb) { cb = opts; opts = null; } if (!opts) opts = {}; return cb (null, new RedisStatsFactory (opts)); } module.exports = creator;