diff --git a/lib/parsers/unix.js b/lib/parsers/unix.js index 6a8f414..64be842 100644 --- a/lib/parsers/unix.js +++ b/lib/parsers/unix.js @@ -9,23 +9,25 @@ module.exports = function() { lines.forEach(function(line) { var trimmed = line.trim().replace(/\s+/g, ' '), data = trimmed && trimmed.split(' ') || [], - pid = data[0] && parseInt(data[0], 10), + pid = data[0], pi = pids.indexOf(pid); if (pid && pi !== -1) { - _this.emit('data', _this._pids[pid], { + data = { cputime: data[1].replace(':', '').replace(':', '.'), memoryUsage: data[2] ? data[2].replace(/[^\d]/g, '') : '0', uptime: new Date().getTime() - _this._pids[pid].meta._started - }) + }; + + _this.emit('data', _this._pids[pid], data) + + _this._collect(_this._pids[pid], data); delete pids[pi]; } }) - console.log(lines, pids); - return pids; } } \ No newline at end of file diff --git a/lib/parsers/win.js b/lib/parsers/win.js index 739fc57..fb0df19 100644 --- a/lib/parsers/win.js +++ b/lib/parsers/win.js @@ -12,13 +12,17 @@ module.exports = function() { pi = pids.indexOf(pid); - if (pid && pi !== -1) { - _this.emit('data', _this._pids[pid], { + if (pid && pi !== -1) { + data = { cputime: data[ data.length - 2 ].replace(':', '').replace(':', '.'), memoryUsage: data[4] ? data[4].replace(/[^\d]/g, '') : '0', uptime: new Date().getTime() - _this._pids[pid].meta._started - }) - + }; + + _this.emit('data', _this._pids[pid], data); + + _this._collect(_this._pids[pid], data); + delete pids[pi]; } }); diff --git a/lib/vitals.js b/lib/vitals.js index c4562e2..9d41722 100644 --- a/lib/vitals.js +++ b/lib/vitals.js @@ -1,6 +1,6 @@ var util = require('util'), os = require('os'), - Parser = os.platform().indexOf('win') != -1 ? require('./parsers/win') : require('./parsers/linux'), + Parser = os.platform().indexOf('win') != -1 ? require('./parsers/win') : require('./parsers/unix'), EventEmitter = require('events').EventEmitter; @@ -11,6 +11,10 @@ function Vitals(options) { options.interval = options.interval || 3000; + options.sampleRate = 0.5; + + options.maxSamples = options.maxSamples || 100; + this.options = options; this.length = 0; @@ -173,6 +177,22 @@ Vitals.prototype._run = function() { }); } +Vitals.prototype._collect = function(proc, data) { + if (! Array.isArray(proc.meta._samples)) { + proc.meta._samples = []; + } + + if (Math.random() <= this.options.sampleRate) { + data.collected = new Date().getTime(); + + proc.meta._samples.push(data); + + if (proc.meta._samples.length > this.options.maxSamples) { + proc.meta._samples = proc.meta._samples.splice(this.options.maxSamples * -1); + } + } +} + Vitals.prototype.emit = function() { var _this = this, args = arguments; diff --git a/readme.md b/readme.md index a2cbe2f..2775450 100644 --- a/readme.md +++ b/readme.md @@ -9,12 +9,15 @@ ## Features - - Windows & Unix support + - Windows & Unix support - Meta data support + - Data sampling ## Options - `interval` the interval in which to poll the processes (Default 3000ms) + - `maxSamples` the maximum number of samples to retain + - `sampleRate` the rate at which to sample data between 0 (disabled) and 1 for 100%, ie: 0.15 for 15% of the time ## Events @@ -80,6 +83,12 @@ Count number of processes being monitored vitals.length ``` +Sampling Data +vitals.on('data', function(proc, data) { + //proc.meta._samples == Array[data, data, data] + //data.collected is the time the sample was collected +}); + Events ```js diff --git a/test/test.js b/test/test.js index 06458a7..b7240fd 100644 --- a/test/test.js +++ b/test/test.js @@ -301,9 +301,7 @@ describe('Health Monitor', function(){ assert.exists(data.uptime); assert.exists(data.memoryUsage); done(); - }); - - health.start(); + }); }) it('should emit#data that contains process meta data', function(done) { @@ -311,9 +309,7 @@ describe('Health Monitor', function(){ assert.exists(proc.meta.custom); assert.equal('meta', proc.meta.custom); done(); - }); - - health.start(); + }); }) afterEach(function() { @@ -326,6 +322,67 @@ describe('Health Monitor', function(){ }) }) + describe('Data Sampling', function() { + before(function() { + health.options.sampleRate = 1; + }); + + beforeEach(function() { + health.add(process.pid, {custom: 'meta'}); + health.start(); + }) + + it('should provide sample data', function(done) { + health.on('data', function(proc, data) { + assert.exists(proc.meta._samples); + assert.equal(1, proc.meta._samples.length); + done(); + }) + }) + + it('should emit#data that contains uptime, cputime, memory usage and timestamp when collected', function(done) { + health.on('data', function(proc, data) { + assert.exists(proc.meta._samples); + assert.exists(proc.meta._samples[0].cputime); + assert.exists(proc.meta._samples[0].uptime); + assert.exists(proc.meta._samples[0].memoryUsage); + assert.exists(proc.meta._samples[0].collected); + done(); + }) + }) + + it('should adhere to the max sampling rate', function(done) { + var count = 0, + until = 10; + + health.options.maxSamples = 3; + + this.timeout(10000); + + health.on('data', function(proc, data) { + ++count; + + assert.exists(proc.meta._samples); + assert.equal(count > 3 ? 3 : count, proc.meta._samples.length); + + if (count >= until) { + assert(count > proc.meta._samples.length); + done(); + } + }) + }) + + afterEach(function() { + health.removeAllListeners('data'); + health.remove(); + health.stop(); + }) + + after(function() { + health.remove(); + }) + }); + describe('Stopping', function() { before(function() { health.add(process.pid, {custom: 'meta'});