diff --git a/.travis.yml b/.travis.yml index ab6236f..4532d48 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,4 +5,4 @@ node_js: notifications: email: - - sam@tixelated.com + - luislhl@gmail.com diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b3cb81..91857a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +### 0.6.3 + * Update example.js. Thanks @khalidsalomao + +### 0.6.2 + * New example and improvements in package.json. Thanks @khalidsalomao + +### 0.6.1 + * New smoothing factor to be used when lag is falling + * Isolate logic of currentLag in an overwritable function + ### 0.5.1 * Set `onLag` default threshould to `maxLag()`. Thanks @flentini diff --git a/README.md b/README.md index 0d4afa1..37366e8 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ -[![Build Status](https://secure.travis-ci.org/STRML/node-toobusy.png)](http://travis-ci.org/STRML/node-toobusy) +[![Build Status](https://secure.travis-ci.org/luislhl/node-toobusy.png)](http://travis-ci.org/luislhl/node-toobusy) +[![npm version](https://badge.fury.io/js/node-toobusy.svg)](https://www.npmjs.com/package/node-toobusy) # Is Your Node Process Too Busy? -`toobusy-js` is a fork of lloyd's [node-toobusy](http://github.com/lloyd/node-toobusy) that removes native dependencies +`node-toobusy` is a fork of lloyd's [node-toobusy](http://github.com/lloyd/node-toobusy) that removes native dependencies in favor of using the `unref` introduced in [node 0.9.1](http://blog.nodejs.org/2012/08/28/node-v0-9-1-unstable/). This package is a simpler install without native dependencies, but requires node >= 0.9.1. @@ -31,14 +32,14 @@ and continue serving as many requests as possible. ## installation ``` -npm install toobusy-js +npm install node-toobusy ``` ## usage ```javascript -var toobusy = require('toobusy-js'), +var toobusy = require('node-toobusy'), express = require('express'); var app = express(); @@ -60,25 +61,22 @@ app.get('/', function(req, res) { }); var server = app.listen(3000); - -process.on('SIGINT', function() { - server.close(); - // calling .shutdown allows your process to exit normally - toobusy.shutdown(); - process.exit(); -}); ``` ## tunable parameters The library exposes a few knobs: -`maxLag` - This number represents the maximum amount of time in milliseconds that the event queue is behind, -before we consider the process *too busy*. -`interval` - The check interval for measuring event loop lag, in ms. +**maxLag** - This number represents the maximum amount of time in milliseconds that the event queue is behind, +before we consider the process *too busy*. +**interval** - The check interval for measuring event loop lag, in ms. +**smoothingFactor** - When a new lag is measured, we smooth its value using the standard [exponential smoothing formula](https://en.wikipedia.org/wiki/Exponential_smoothing). +There are two factors available, the smoothingFactorOnRise, which is used when the new lag is higher than currentLag, and the smoothingFactorOnFall, which is used when the new lag is lower than currentLag. +It's a good idea to keep the factor on fall higher than on rise, to make the currentLag recover faster after spikes. +**lagFunction** - This is the function used to calculate currentLag. You can overwrite it if you need a different behavior. The parameters passed to it are: `lag`, `currentLag`, `smoothingFactorOnRise` and `smoothingFactorOnFall`. ```javascript -var toobusy = require('toobusy-js'); +var toobusy = require('node-toobusy'); // Set maximum lag to an aggressive value. toobusy.maxLag(10); @@ -87,6 +85,21 @@ toobusy.maxLag(10); // but may cause the check to be too sensitive. toobusy.interval(250); +// Set smoothing factor on rise to a lower value. This will make it less sensible +// to spikes. Default is 1/3. +toobusy.smoothingFactorOnRise(1/4); + +// Set smoothing factor on fall to a higher value. This will make it recover faster +// after spikes. Default is 2/3. +toobusy.smoothingFactorOnFall(3/4); + +// You can overwrite this function to change the way currentLag is calculated. +// This is the default implementation. +toobusy.lagFunction = function(lag, cLag, sFactorRise, sFactorFall) { + var factor = lag > cLag ? sFactorRise : sFactorFall; + return factor * lag + (1 - factor) * cLag; +} + // Get current maxLag or interval setting by calling without parameters. var currentMaxLag = toobusy.maxLag(), interval = toobusy.interval(); @@ -108,7 +121,7 @@ The default of 70 should get you started. ## Events -As of `0.5.0`, `toobusy-js` exposes an `onLag` method. Pass it a callback to be notified when +As of `0.5.0`, `node-toobusy` exposes an `onLag` method. Pass it a callback to be notified when a slow event loop tick has been detected. ## references @@ -122,4 +135,4 @@ this concept is not new. Here are references to others who apply the same techn ## license -[WTFPL](http://wtfpl.org) +[WTFPL](http://wtfpl.net) diff --git a/examples/example.js b/examples/example.js new file mode 100644 index 0000000..b7a7ffe --- /dev/null +++ b/examples/example.js @@ -0,0 +1,37 @@ +var toobusy = require('node-toobusy'); + +// Set maximum lag to an aggressive value. +toobusy.maxLag(10); + +// Set check interval to a faster value. This will catch more latency spikes +// but may cause the check to be too sensitive. +toobusy.interval(250); + +// Set smoothing factor on rise to a lower value. This will make it less sensible +// to spikes. Default is 1/3. +toobusy.smoothingFactorOnRise(1/4); + +// Set smoothing factor on fall to a higher value. This will make it recover faster +// after spikes. Default is 2/3. +toobusy.smoothingFactorOnFall(3/4); + +// You can overwrite this function to change the way currentLag is calculated. +// This is the default implementation. +toobusy.lagFunction = function(lag, currentLag, smoothingUp, smoothingDown) { + var factor = lag > currentLag ? smoothingUp : smoothingDown; + return factor * lag + (1 - factor) * currentLag; +} + +// Get current maxLag or interval setting by calling without parameters. +var currentMaxLag = toobusy.maxLag(), interval = toobusy.interval(); + +toobusy.onLag(function(currentLag) { + console.log("Event loop lag detected! Latency: " + currentLag + "ms"); +}); + +// check if node is too busy +if (toobusy()) { + console.warn("TooBusy!"); +} + +console.log("Current adjusted event lag", toobusy.lag(), "ms"); diff --git a/package.json b/package.json index e49e3b2..ebe3770 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "toobusy-js", + "name": "node-toobusy", "description": "Don't fall over when your Node.JS server is too busy. Now without native dependencies!", - "homepage": "https://github.com/STRML/node-toobusy", - "version": "0.5.1", + "homepage": "https://github.com/luislhl/node-toobusy#readme", + "version": "0.6.3", "dependencies": {}, "devDependencies": { "mocha": "1.7.0", @@ -10,14 +10,29 @@ "should": "1.2.1" }, "maintainers": [ - "Samuel Reed " + "Samuel Reed ", + "Luis Helder " ], "license": "WTFPL", - "repository": "STRML/node-toobusy", + "repository": { + "type": "git", + "url": "https://github.com/luislhl/node-toobusy.git" + }, "engines": { "node": ">=0.9.1" }, "scripts": { + "prepublishOnly": "npm test", "test": "mocha tests" - } + }, + "keywords": [ + "toobusy", + "node-toobusy", + "event", + "loop", + "lag", + "maxlag", + "busy" + ], + "tonicExampleFilename": "examples/example.js" } diff --git a/tests.js b/tests.js index 19548eb..8f0c366 100644 --- a/tests.js +++ b/tests.js @@ -91,6 +91,9 @@ describe('toobusy()', function() { }); describe('lag events', function () { + //Sometimes the default 2s timeout is hit on this suite, raise to 10s. + this.timeout(10 * 1000); + it('should not emit lag events if the lag is less than the configured threshold', testLagEvent(100, 50, false)); it('should emit lag events if the lag is greater than the configured threshold', @@ -140,6 +143,7 @@ describe('smoothingFactor', function() { this.timeout(10 * 1000); beforeEach(function() { + toobusy.reset(); toobusy.maxLag(10); toobusy.interval(250); }); @@ -147,26 +151,23 @@ describe('smoothingFactor', function() { toobusy.maxLag(70); toobusy.interval(500); }); - it('should default to 1/3', function(done) { - (toobusy.smoothingFactor()).should.equal(1/3); - done(); - }); - it('should throw an exception for invalid values', function(done) { - (function() { toobusy.smoothingFactor(0); }).should.throw; - (function() { toobusy.smoothingFactor(2); }).should.throw; - (function() { toobusy.smoothingFactor(-1); }).should.throw; - (function() { toobusy.smoothingFactor(1); }).should.not.throw; - done(); + + setupSmoothingFactorTests({ + function: toobusy.smoothingFactorOnRise, + suiteName: 'on rise', + default: 1/3 }); - it('should be configurable', function(done) { - (toobusy.smoothingFactor(0.9)).should.equal(0.9); - (toobusy.smoothingFactor(0.1)).should.equal(0.1); - (toobusy.smoothingFactor()).should.equal(0.1); - done(); + + setupSmoothingFactorTests({ + function: toobusy.smoothingFactorOnFall, + suiteName: 'on fall', + default: 1 - 1/3 }); - it('should allow no dampening', function(done) { + + it('should allow no dampening', function (done) { var cycles_to_toobusy = 0; - toobusy.smoothingFactor(1); // no dampening + toobusy.smoothingFactorOnRise(1); // no dampening + toobusy.smoothingFactorOnFall(1); // no dampening function load() { if (toobusy()) { @@ -180,9 +181,9 @@ describe('smoothingFactor', function() { load(); }); - it('should respect larger dampening factors', function(done) { + it('should respect larger dampening factors', function (done) { var cycles_to_toobusy = 0; - toobusy.smoothingFactor(0.05); + toobusy.smoothingFactorOnRise(0.05); function load() { if (toobusy()) { @@ -196,8 +197,33 @@ describe('smoothingFactor', function() { load(); }); + }); +function setupSmoothingFactorTests(options) { + var smoothingFunc = options.function; + + describe(options.suiteName, function() { + it('should default to ' + options.default, function(done) { + (smoothingFunc()).should.equal(options.default); + done(); + }); + it('should throw an exception for invalid values', function(done) { + (function() { smoothingFunc(0); }).should.throw; + (function() { smoothingFunc(2); }).should.throw; + (function() { smoothingFunc(-1); }).should.throw; + (function() { smoothingFunc(1); }).should.not.throw; + done(); + }); + it('should be configurable', function(done) { + (smoothingFunc(0.9)).should.equal(0.9); + (smoothingFunc(0.1)).should.equal(0.1); + (smoothingFunc()).should.equal(0.1); + done(); + }); + }); +}; + describe('started', function() { it('should return false after shutdown', function(done) { toobusy.shutdown(); diff --git a/toobusy.js b/toobusy.js index 1f6f7ac..ffff48c 100644 --- a/toobusy.js +++ b/toobusy.js @@ -22,7 +22,8 @@ var SMOOTHING_FACTOR = 1/3; var lastTime = Date.now(); var highWater = STANDARD_HIGHWATER; var interval = STANDARD_INTERVAL; -var smoothingFactor = SMOOTHING_FACTOR; +var smoothingFactorOnRise = SMOOTHING_FACTOR; +var smoothingFactorOnFall = 1 - SMOOTHING_FACTOR; var currentLag = 0; var checkInterval; var lagEventThreshold = -1; @@ -40,6 +41,17 @@ var toobusy = function(){ return Math.random() < pctToBlock; }; +/** + * Resets variables to their default values + * Affected variables: highWater, interval, smoothingFactorOnRise and smoothingFactorOnFall + */ +toobusy.reset = function() { + highWater = STANDARD_HIGHWATER; + interval = STANDARD_INTERVAL; + smoothingFactorOnRise = SMOOTHING_FACTOR; + smoothingFactorOnFall = 1 - SMOOTHING_FACTOR; +}; + /** * Sets or gets the current check interval. * If you want more sensitive checking, set a faster (lower) interval. A lower maxLag can also create a more @@ -92,7 +104,24 @@ toobusy.maxLag = function(newLag){ }; /** - * Set or get the smoothing factor. Default is 0.3333.... + * Private function used to set the two smoothing factors + * @param {number} newFactor + * @param {boolean} onFallSelector Optional parameter used to selected which factor to set + */ +function setSmoothingFactor(newFactor, onFallSelector){ + if (typeof newFactor !== "number") throw new Error("NewFactor must be a number."); + if(newFactor <= 0 || newFactor > 1) throw new Error("Smoothing factor should be in range ]0,1]."); + + if (onFallSelector) { + smoothingFactorOnFall = newFactor; + } else { + smoothingFactorOnRise = newFactor; + } +}; + +/** + * Set or get the smoothing factor on rise. Default is 0.3333.... + * This is the factor applied when the measured lag is higher than currentLag * * The smoothing factor per the standard exponential smoothing formula "αtn + (1-α)tn-1" * See: https://en.wikipedia.org/wiki/Exponential_smoothing @@ -100,14 +129,23 @@ toobusy.maxLag = function(newLag){ * @param {Number} [newFactor] New smoothing factor. * @return {Number} New or existing smoothing factor. */ -toobusy.smoothingFactor = function(newFactor){ - if(!newFactor) return smoothingFactor; - - if (typeof newFactor !== "number") throw new Error("NewFactor must be a number."); - if(newFactor <= 0 || newFactor > 1) throw new Error("Smoothing factor should be in range ]0,1]."); +toobusy.smoothingFactorOnRise = function(newFactor) { + if(!newFactor) return smoothingFactorOnRise; + setSmoothingFactor(newFactor); + return smoothingFactorOnRise; +}; - smoothingFactor = newFactor; - return smoothingFactor; +/** + * Set or get the smoothing factor on fall. Default is 0.6666.... + * This is the factor applied when the measured lag is lower than currentLag + * + * @param {Number} [newFactor] New smoothing factor. + * @return {Number} New or existing smoothing factor. + */ +toobusy.smoothingFactorOnFall = function(newFactor) { + if(!newFactor) return smoothingFactorOnFall; + setSmoothingFactor(newFactor, true); + return smoothingFactorOnFall; }; /** @@ -142,6 +180,19 @@ toobusy.onLag = function (fn, threshold) { eventEmitter.on(LAG_EVENT, fn); }; +/** + * Calculates the new value that will be assigned to currentLag + * Overwrite this function if you need a different behavior + * @param {number} lag The last measured lag + * @param {number} cLag The currentLag value + * @param {number} sFactor The smoothingFactor value + * @return {number} The calculated value + */ +toobusy.lagFunction = function (lag, cLag, sFactorRise, sFactorFall) { + var factor = lag > cLag ? sFactorRise : sFactorFall; + return factor * lag + (1 - factor) * cLag; +} + /** * Private - starts checking lag. */ @@ -150,8 +201,7 @@ function start() { var now = Date.now(); var lag = now - lastTime; lag = Math.max(0, lag - interval); - // Dampen lag. See SMOOTHING_FACTOR initialization at the top of this file. - currentLag = smoothingFactor * lag + (1 - smoothingFactor) * currentLag; + currentLag = toobusy.lagFunction(lag, currentLag, smoothingFactorOnRise, smoothingFactorOnFall); lastTime = now; if (lagEventThreshold !== -1 && currentLag > lagEventThreshold) {