├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── examples ├── express.js ├── http.js ├── load.js ├── package.json └── standalone.js ├── index.js ├── package.json ├── tests.js ├── toobusy.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | *~ 3 | /examples/node_modules 4 | npm-debug.log -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | yarn.lock 2 | examples/ 3 | tests.js 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 0.10 5 | 6 | notifications: 7 | email: 8 | - sam@tixelated.com 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.5.1 2 | * Set `onLag` default threshould to `maxLag()`. Thanks @flentini 3 | 4 | ### 0.5.0 5 | * Add `toobusy.onLag()` for catching lag events. Thanks @mnahkies 6 | 7 | ### 0.4.3 8 | * Update dampening formula and make smoothing tunable. Thanks @tgroleau 9 | 10 | ### 0.4.2 11 | * Input validation for `maxLag()` and `interval()`. 12 | 13 | ### 0.4.1 14 | * Fix checking not automatically starting. 15 | 16 | ### 0.4.0 17 | * Refactoring 18 | * Added `toobusy.interval()` for getting/setting check interval. 19 | 20 | ### 0.3.0 21 | * full JS implementation, removed c code & bindings 22 | * example bugfixes 23 | 24 | ### 0.2.3 25 | * node.js 0.10.0 support upgrade to latest bindings and include in .travis.yml 26 | 27 | ### 0.2.2 28 | * *really* works on windows 29 | * improved documentation 30 | 31 | ### 0.2.1 32 | * works on windows 33 | * improved express.js example (now targets express 3.0) 34 | 35 | ### 0.2.0 36 | * documentation improvements 37 | * improved examples (including load generation tool and server) 38 | * .lag() added to retrieve (periodically calculated) event loop lag in ms 39 | 40 | ### 0.1.1 41 | * hey look, unit tests! 42 | 43 | ### 0.1.0 44 | * improved documentation and samples 45 | * added `maxLag` parameter 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://secure.travis-ci.org/STRML/node-toobusy.png)](http://travis-ci.org/STRML/node-toobusy) 2 | 3 | # Is Your Node Process Too Busy? 4 | 5 | `toobusy-js` is a fork of lloyd's [node-toobusy](http://github.com/lloyd/node-toobusy) that removes native dependencies 6 | in favor of using the `unref` introduced in [node 0.9.1](http://blog.nodejs.org/2012/08/28/node-v0-9-1-unstable/). 7 | 8 | This package is a simpler install without native dependencies, but requires node >= 0.9.1. 9 | 10 | ## Node-Toobusy 11 | 12 | What happens when your service is overwhelmed with traffic? 13 | Your server can do one of two things: 14 | 15 | * Stop working, or... 16 | * Keep serving as many requests as possible 17 | 18 | This library helps you do the latter. 19 | 20 | ## How it works 21 | 22 | `toobusy` polls the node.js event loop and keeps track of "lag", 23 | which is long requests wait in node's event queue to be processed. 24 | When lag crosses a threshold, `toobusy` tells you that you're *too busy*. 25 | At this point you can stop request processing early 26 | (before you spend too much time on them and compound the problem), 27 | and return a "Server Too Busy" response. 28 | This allows your server to stay *responsive* under extreme load, 29 | and continue serving as many requests as possible. 30 | 31 | ## installation 32 | 33 | ``` 34 | npm install toobusy-js 35 | ``` 36 | 37 | 38 | ## usage 39 | 40 | ```javascript 41 | var toobusy = require('toobusy-js'), 42 | express = require('express'); 43 | 44 | var app = express(); 45 | 46 | // middleware which blocks requests when we're too busy 47 | app.use(function(req, res, next) { 48 | if (toobusy()) { 49 | res.send(503, "I'm busy right now, sorry."); 50 | } else { 51 | next(); 52 | } 53 | }); 54 | 55 | app.get('/', function(req, res) { 56 | // processing the request requires some work! 57 | var i = 0; 58 | while (i < 1e5) i++; 59 | res.send("I counted to " + i); 60 | }); 61 | 62 | var server = app.listen(3000); 63 | 64 | process.on('SIGINT', function() { 65 | server.close(); 66 | // calling .shutdown allows your process to exit normally 67 | toobusy.shutdown(); 68 | process.exit(); 69 | }); 70 | ``` 71 | 72 | ## tunable parameters 73 | 74 | The library exposes a few knobs: 75 | 76 | `maxLag` - This number represents the maximum amount of time in milliseconds that the event queue is behind, 77 | before we consider the process *too busy*. 78 | `interval` - The check interval for measuring event loop lag, in ms. 79 | 80 | ```javascript 81 | var toobusy = require('toobusy-js'); 82 | 83 | // Set maximum lag to an aggressive value. 84 | toobusy.maxLag(10); 85 | 86 | // Set check interval to a faster value. This will catch more latency spikes 87 | // but may cause the check to be too sensitive. 88 | toobusy.interval(250); 89 | 90 | // Get current maxLag or interval setting by calling without parameters. 91 | var currentMaxLag = toobusy.maxLag(), interval = toobusy.interval(); 92 | 93 | toobusy.onLag(function(currentLag) { 94 | console.log("Event loop lag detected! Latency: " + currentLag + "ms"); 95 | }); 96 | ``` 97 | 98 | The default maxLag value is 70ms, and the default check interval is 500ms. 99 | This allows an "average" server to run at 90-100% CPU 100 | and keeps request latency at around 200ms. 101 | For comparison, a maxLag value of 10ms results in 60-70% CPU usage, 102 | while latency for "average" requests stays at about 40ms. 103 | 104 | These numbers are only examples, 105 | and the specifics of your hardware and application can change them drastically, 106 | so experiment! 107 | The default of 70 should get you started. 108 | 109 | ## Events 110 | 111 | As of `0.5.0`, `toobusy-js` exposes an `onLag` method. Pass it a callback to be notified when 112 | a slow event loop tick has been detected. 113 | 114 | ## references 115 | 116 | > There is nothing new under the sun. (Ecclesiastes 1:9) 117 | 118 | Though applying "event loop latency" to node.js was not directly inspired by anyone else's work, 119 | this concept is not new. Here are references to others who apply the same technique: 120 | 121 | * [Provos, Lever, and Tweedie 2000](http://www.kegel.com/c10k.html#tips) - "notes that dropping incoming connections when the server is overloaded improved the shape of the performance curve." 122 | 123 | ## license 124 | 125 | [WTFPL](http://wtfpl.org) 126 | -------------------------------------------------------------------------------- /examples/express.js: -------------------------------------------------------------------------------- 1 | var toobusy = require('..'), 2 | express = require('express'); 3 | 4 | var app = express(); 5 | 6 | // Have grace under load 7 | app.use(function(req, res, next) { 8 | if (toobusy()) { 9 | res.send(503, "I'm busy right now, sorry."); 10 | } else { 11 | next(); 12 | } 13 | }); 14 | 15 | app.get('/', function(req, res) { 16 | // processing the request requires some work! 17 | var i = 0; 18 | while (i < 1e5) i++; 19 | res.send("I counted to " + i); 20 | }); 21 | 22 | var server = app.listen(3000); 23 | 24 | process.on('SIGINT', function() { 25 | server.close(); 26 | toobusy.shutdown(); 27 | }); 28 | -------------------------------------------------------------------------------- /examples/http.js: -------------------------------------------------------------------------------- 1 | var http = require('http'), 2 | toobusy = require('..'); 3 | 4 | console.log("Maximum allowed event loop lag: " + toobusy.maxLag(50) + "ms"); 5 | 6 | function processRequest(res, num, startTime) { 7 | if (!startTime) startTime = new Date(); 8 | 9 | if (num === undefined) { 10 | return process.nextTick(function() { 11 | processRequest(res, 0); 12 | }); 13 | } 14 | if (num >= 5) { 15 | res.writeHead(200, {'Content-Type': 'text/plain'}); 16 | res.end('I counted to ' + num + ' in ' + (new Date() - startTime) + 'ms\n'); 17 | } else { 18 | // 1ms of computation 19 | var targetTime = (new Date() - startTime) + 1; 20 | while (new Date() - startTime < targetTime); 21 | processRequest(res, num + 1, startTime); 22 | } 23 | } 24 | 25 | http.createServer(function (req, res) { 26 | if (toobusy()) { 27 | res.writeHead(503); 28 | return res.end(); 29 | } 30 | 31 | // we're not too busy! let's process a request! 32 | processRequest(res); 33 | }).listen(3000, '127.0.0.1', 2048); 34 | -------------------------------------------------------------------------------- /examples/load.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | 3 | // a little load generate client that generates constant load over 4 | // even if the server cannot keep up 5 | 6 | var running = 0; 7 | var twoHundred = 0; 8 | var fiveOhThree = 0; 9 | var yucky = 0; 10 | var avg = 0; 11 | 12 | // how many requests per second should we run? 13 | const rps = (process.env['RPS'] || 40) / 40; 14 | var curRPS = rps; 15 | var started = 0; 16 | const startTime = new Date(); 17 | var lastMark = startTime; 18 | 19 | var ivalnum = 0; 20 | 21 | setInterval(function() { 22 | ivalnum++; 23 | function startOne() { 24 | started++; 25 | running++; 26 | var start = new Date(); 27 | var endOrError = false; 28 | function cEndOrError() { 29 | if (endOrError) console.log("end AND error"); 30 | endOrError = true; 31 | } 32 | var req = http.get({ 33 | host: '127.0.0.1', 34 | port: 3000, 35 | agent: false, 36 | path: '/', 37 | headers: { 38 | "connection": "close" 39 | } 40 | }, function(res) { 41 | if (res.statusCode === 503) { 42 | fiveOhThree++; 43 | } else { 44 | twoHundred++; 45 | } 46 | avg = ((new Date() - start) + avg * started) / (started + 1); 47 | running--; 48 | cEndOrError(); 49 | }).on('error', function(e) { 50 | process.stderr.write(e.toString() + " - " + (new Date() - start) + "ms\n"); 51 | avg = ((new Date() - start) + avg * started) / (started + 1); 52 | running--; 53 | yucky++; 54 | cEndOrError(); 55 | }); 56 | } 57 | 58 | for (var i = 0; i < curRPS ; i++) startOne(); 59 | 60 | // report and scale up every 2s 61 | if (!(ivalnum % (40 * 2))) { 62 | var delta = (new Date() - lastMark) / 1000.0 ; 63 | console.log(Math.round((new Date() - startTime) / 1000.0), 64 | Math.round(started / delta), 65 | Math.round(twoHundred / delta), 66 | Math.round(fiveOhThree / delta), 67 | avg, 68 | Math.round(yucky / delta)); 69 | curRPS = curRPS + .5; 70 | started = twoHundred = fiveOhThree = yucky = 0; 71 | lastMark = new Date(); 72 | } 73 | }, 25); 74 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "toobusy-examples", 3 | "version": "0.0.1", 4 | "description": "dependencies required by the toobusy examples", 5 | "private": true, 6 | "dependencies": { 7 | "express": "3" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/standalone.js: -------------------------------------------------------------------------------- 1 | // first, we want to be able to get cpu usage stats in terms of percentage 2 | var loaded = false; 3 | var toobusy = require('..'); 4 | 5 | var work = 524288; 6 | 7 | function worky() { 8 | var howBusy = toobusy(); 9 | if (howBusy) { 10 | work /= 4; 11 | console.log("I can't work! I'm too busy:", toobusy.lag() + "ms behind"); 12 | } 13 | work *= 2; 14 | for (var i = 0; i < work;) i++; 15 | console.log("worked:", work); 16 | }; 17 | 18 | var interval = setInterval(worky, 100); 19 | 20 | process.on('SIGINT', function() { 21 | clearInterval(interval); 22 | toobusy.shutdown(); 23 | }); 24 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./toobusy'); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "toobusy-js", 3 | "description": "Don't fall over when your Node.JS server is too busy. Now without native dependencies!", 4 | "homepage": "https://github.com/STRML/node-toobusy", 5 | "version": "0.5.1", 6 | "dependencies": {}, 7 | "devDependencies": { 8 | "mocha": "^3.5.0", 9 | "pre-commit": "^1.0.5", 10 | "should": "^12.0.0" 11 | }, 12 | "maintainers": [ 13 | "Samuel Reed " 14 | ], 15 | "license": "WTFPL", 16 | "repository": "STRML/node-toobusy", 17 | "engines": { 18 | "node": ">=0.9.1" 19 | }, 20 | "scripts": { 21 | "test": "mocha tests" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var should = require('should'); 3 | var toobusy = require('./'); 4 | 5 | function tightWork(duration) { 6 | var start = Date.now(); 7 | while ((Date.now() - start) < duration) { 8 | for (var i = 0; i < 1e5;) i++; 9 | } 10 | } 11 | 12 | /*global describe, it, beforeEach, afterEach */ 13 | describe('the library', function() { 14 | it('should export a couple functions', function() { 15 | should(toobusy).be.Function(); 16 | (toobusy.maxLag).should.be.Function(); 17 | (toobusy.shutdown).should.be.Function(); 18 | (toobusy.interval).should.be.Function(); 19 | (toobusy.shutdown).should.be.Function(); 20 | (toobusy).should.not.have.property('start'); 21 | }); 22 | it('should start automatically', function() { 23 | (toobusy.started()).should.equal(true); 24 | }); 25 | }); 26 | 27 | describe('maxLag', function() { 28 | it('should default to 70', function() { 29 | (toobusy.maxLag()).should.equal(70); 30 | }); 31 | it('should throw an exception for non-numbers', function() { 32 | (function() { toobusy.maxLag('derp'); }).should.throw(/must be a number/); 33 | }); 34 | it('should throw an exception for values < 10', function() { 35 | (function() { toobusy.maxLag(9); }).should.throw(/should be greater than 10/); 36 | }); 37 | it('should be configurable', function() { 38 | (toobusy.maxLag(50)).should.equal(50); 39 | (toobusy.maxLag(10)).should.equal(10); 40 | (toobusy.maxLag()).should.equal(10); 41 | }); 42 | }); 43 | 44 | describe('interval', function() { 45 | it('should default to 500', function() { 46 | (toobusy.interval()).should.equal(500); 47 | }); 48 | it('should throw an exception for values < 16', function() { 49 | (function() { toobusy.interval(15); }).should.throw(/Interval/); 50 | }); 51 | it('should be configurable', function() { 52 | (toobusy.interval(250)).should.equal(250); 53 | (toobusy.interval(300)).should.equal(300); 54 | (toobusy.interval()).should.equal(300); 55 | }); 56 | }); 57 | 58 | describe('toobusy()', function() { 59 | // Set lower thresholds for each of these tests. 60 | // Resetting the interval() also resets the internal lag counter, which 61 | // is nice for making these tests independent of each other. 62 | beforeEach(function() { 63 | toobusy.maxLag(10); 64 | toobusy.interval(50); 65 | }); 66 | after(function() { 67 | toobusy.maxLag(70); 68 | toobusy.interval(500); 69 | }); 70 | it('should return true after a little load', function(done) { 71 | function load() { 72 | if (toobusy()) return done(); 73 | tightWork(100); 74 | setTimeout(load, 0); 75 | } 76 | load(); 77 | }); 78 | 79 | it('should return a lag value after a little load', function(done) { 80 | function load() { 81 | if (toobusy()) { 82 | var lag = toobusy.lag(); 83 | should.exist(lag); 84 | lag.should.be.above(1); 85 | return done(); 86 | } 87 | tightWork(100); 88 | setTimeout(load, 0); 89 | } 90 | load(); 91 | }); 92 | 93 | describe('lag events', function () { 94 | it('should not emit lag events if the lag is less than the configured threshold', 95 | testLagEvent(100, 50, false)); 96 | it('should emit lag events if the lag is greater than the configured threshold', 97 | testLagEvent(50, 150, true)); 98 | it('should emit lag events if lag occurs and no threshold is specified', 99 | testLagEvent(undefined, 500, true)); 100 | 101 | function testLagEvent(threshold, work, expectFire) { 102 | return function (done) { 103 | var calledDone = false; 104 | var finish = function() { 105 | if (calledDone) return; 106 | calledDone = true; 107 | toobusy.shutdown(); // stops onLag() from firing again 108 | clearTimeout(workTimeout); 109 | done.apply(null, arguments); 110 | }; 111 | 112 | toobusy.onLag(function (lag) { 113 | if (!expectFire) { 114 | return finish(new Error('lag event fired unexpectedly')); 115 | } 116 | 117 | should.exist(lag); 118 | lag.should.be.above(threshold || 0); 119 | finish(); 120 | }, threshold); 121 | 122 | if (!expectFire) { 123 | setTimeout(function () { 124 | finish(); 125 | }, work + threshold); 126 | } 127 | 128 | // Do work 3x to work around smoothing factor 129 | var count = 0; 130 | var workTimeout = setTimeout(function working() { 131 | tightWork(work); 132 | if (++count < 3) workTimeout = setTimeout(working); 133 | }) 134 | } 135 | } 136 | }); 137 | }); 138 | 139 | describe('smoothingFactor', function() { 140 | // Sometimes the default 2s timeout is hit on this suite, raise to 10s. 141 | this.timeout(10 * 1000); 142 | 143 | beforeEach(function() { 144 | toobusy.maxLag(10); 145 | toobusy.interval(250); 146 | }); 147 | after(function() { 148 | toobusy.maxLag(70); 149 | toobusy.interval(500); 150 | }); 151 | it('should default to 1/3', function() { 152 | (toobusy.smoothingFactor()).should.equal(1/3); 153 | }); 154 | it('should throw an exception for invalid values', function() { 155 | (function() { toobusy.smoothingFactor(0); }).should.throw; 156 | (function() { toobusy.smoothingFactor(2); }).should.throw; 157 | (function() { toobusy.smoothingFactor(-1); }).should.throw; 158 | (function() { toobusy.smoothingFactor(1); }).should.not.throw; 159 | }); 160 | it('should be configurable', function() { 161 | (toobusy.smoothingFactor(0.9)).should.equal(0.9); 162 | (toobusy.smoothingFactor(0.1)).should.equal(0.1); 163 | (toobusy.smoothingFactor()).should.equal(0.1); 164 | }); 165 | it('should allow no dampening', function(done) { 166 | var cycles_to_toobusy = 0; 167 | toobusy.smoothingFactor(1); // no dampening 168 | 169 | function load() { 170 | if (toobusy()) { 171 | (cycles_to_toobusy).should.equal(3); 172 | return done(); 173 | } 174 | cycles_to_toobusy++; 175 | tightWork(100); // in 3 ticks, will overshoot by ~50ms, above 2*10ms 176 | setImmediate(load); 177 | } 178 | 179 | load(); 180 | }); 181 | it('should respect larger dampening factors', function(done) { 182 | var cycles_to_toobusy = 0; 183 | toobusy.smoothingFactor(0.05); 184 | 185 | function load() { 186 | if (toobusy()) { 187 | (cycles_to_toobusy).should.be.above(3); 188 | return done(); 189 | } 190 | cycles_to_toobusy++; 191 | tightWork(100); 192 | setImmediate(load); 193 | } 194 | 195 | load(); 196 | }); 197 | }); 198 | 199 | describe('started', function() { 200 | it('should return false after shutdown', function(done) { 201 | toobusy.shutdown(); 202 | (toobusy.started()).should.equal(false); 203 | done(); 204 | }); 205 | }); 206 | 207 | 208 | -------------------------------------------------------------------------------- /toobusy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var events = require('events'); 4 | 5 | // 6 | // Constants 7 | // 8 | var STANDARD_HIGHWATER = 70; 9 | var STANDARD_INTERVAL = 500; 10 | var LAG_EVENT = "LAG_EVENT"; 11 | 12 | // A dampening factor. When determining average calls per second or 13 | // current lag, we weigh the current value against the previous value 2:1 14 | // to smooth spikes. 15 | // See https://en.wikipedia.org/wiki/Exponential_smoothing 16 | var SMOOTHING_FACTOR = 1/3; 17 | 18 | // 19 | // Vars 20 | // 21 | 22 | var lastTime; 23 | var highWater = STANDARD_HIGHWATER; 24 | var interval = STANDARD_INTERVAL; 25 | var smoothingFactor = SMOOTHING_FACTOR; 26 | var currentLag = 0; 27 | var checkInterval; 28 | var lagEventThreshold = -1; 29 | var eventEmitter = new events.EventEmitter(); 30 | 31 | /** 32 | * Main export function. 33 | * @return {Boolean} True if node process is too busy. 34 | */ 35 | var toobusy = function(){ 36 | // If current lag is < 2x the highwater mark, we don't always call it 'too busy'. E.g. with a 50ms lag 37 | // and a 40ms highWater (1.25x highWater), 25% of the time we will block. With 80ms lag and a 40ms highWater, 38 | // we will always block. 39 | var pctToBlock = (currentLag - highWater) / highWater; 40 | return Math.random() < pctToBlock; 41 | }; 42 | 43 | /** 44 | * Sets or gets the current check interval. 45 | * If you want more sensitive checking, set a faster (lower) interval. A lower maxLag can also create a more 46 | * sensitive check. 47 | * @param {Number} [newInterval] New interval to set. If not provided, will return the existing interval. 48 | * @return {Number} New or existing interval. 49 | */ 50 | toobusy.interval = function(newInterval) { 51 | if (!newInterval) return interval; 52 | if (typeof newInterval !== "number") throw new Error("Interval must be a number."); 53 | 54 | newInterval = Math.round(newInterval); 55 | if(newInterval < 16) throw new Error("Interval should be greater than 16ms."); 56 | 57 | currentLag = 0; 58 | interval = newInterval; 59 | start(); 60 | return interval; 61 | }; 62 | 63 | /** 64 | * Returns last lag reading from last check interval. 65 | * @return {Number} Lag in ms. 66 | */ 67 | toobusy.lag = function(){ 68 | return Math.round(currentLag); 69 | }; 70 | 71 | /** 72 | * Set or get the current max latency threshold. Default is 70ms. 73 | * 74 | * Note that if event loop lag goes over this threshold, the process is not always 'too busy' - the farther 75 | * it goes over the threshold, the more likely the process will be considered too busy. 76 | * 77 | * The percentage is equal to the percent over the max lag threshold. So 1.25x over the maxLag will indicate 78 | * too busy 25% of the time. 2x over the maxLag threshold will indicate too busy 100% of the time. 79 | * @param {Number} [newLag] New maxLag (highwater) threshold. 80 | * @return {Number} New or existing maxLag (highwater) threshold. 81 | */ 82 | toobusy.maxLag = function(newLag){ 83 | if(!newLag) return highWater; 84 | 85 | // If an arg was passed, try to set highWater. 86 | if (typeof newLag !== "number") throw new Error("MaxLag must be a number."); 87 | newLag = Math.round(newLag); 88 | if(newLag < 10) throw new Error("Maximum lag should be greater than 10ms."); 89 | 90 | highWater = newLag; 91 | return highWater; 92 | }; 93 | 94 | /** 95 | * Set or get the smoothing factor. Default is 0.3333.... 96 | * 97 | * The smoothing factor per the standard exponential smoothing formula "αtn + (1-α)tn-1" 98 | * See: https://en.wikipedia.org/wiki/Exponential_smoothing 99 | * 100 | * @param {Number} [newFactor] New smoothing factor. 101 | * @return {Number} New or existing smoothing factor. 102 | */ 103 | toobusy.smoothingFactor = function(newFactor){ 104 | if(!newFactor) return smoothingFactor; 105 | 106 | if (typeof newFactor !== "number") throw new Error("NewFactor must be a number."); 107 | if(newFactor <= 0 || newFactor > 1) throw new Error("Smoothing factor should be in range ]0,1]."); 108 | 109 | smoothingFactor = newFactor; 110 | return smoothingFactor; 111 | }; 112 | 113 | /** 114 | * Shuts down toobusy. 115 | * 116 | * Not necessary to call this manually, only do this if you know what you're doing. `unref()` is called 117 | * on toobusy's check interval, so it will never keep the server open. 118 | */ 119 | toobusy.shutdown = function(){ 120 | currentLag = 0; 121 | checkInterval = clearInterval(checkInterval); 122 | eventEmitter.removeAllListeners(LAG_EVENT); 123 | }; 124 | 125 | toobusy.started = function() { 126 | return Boolean(checkInterval != null); 127 | }; 128 | 129 | /** 130 | * Registers an event listener for lag events, 131 | * optionally specify a minimum value threshold for events being emitted 132 | * @param {Function} fn Function of form onLag(value: number) => void 133 | * @param {number} [threshold=maxLag] Optional minimum lag value for events to be emitted 134 | */ 135 | toobusy.onLag = function (fn, threshold) { 136 | 137 | if (typeof threshold === "number") { 138 | lagEventThreshold = threshold; 139 | } else { 140 | lagEventThreshold = toobusy.maxLag(); 141 | } 142 | 143 | eventEmitter.on(LAG_EVENT, fn); 144 | }; 145 | 146 | /** 147 | * Private - starts checking lag. 148 | */ 149 | function start() { 150 | lastTime = Date.now(); 151 | 152 | clearInterval(checkInterval); 153 | checkInterval = setInterval(function(){ 154 | var now = Date.now(); 155 | var lag = now - lastTime; 156 | lag = Math.max(0, lag - interval); 157 | // Dampen lag. See SMOOTHING_FACTOR initialization at the top of this file. 158 | currentLag = smoothingFactor * lag + (1 - smoothingFactor) * currentLag; 159 | lastTime = now; 160 | 161 | if (lagEventThreshold !== -1 && currentLag > lagEventThreshold) { 162 | eventEmitter.emit(LAG_EVENT, currentLag); 163 | } 164 | 165 | }, interval); 166 | 167 | // Don't keep process open just for this timer. 168 | checkInterval.unref(); 169 | } 170 | 171 | // Kickoff the checking! 172 | start(); 173 | 174 | module.exports = toobusy; 175 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | balanced-match@^1.0.0: 6 | version "1.0.0" 7 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 8 | 9 | brace-expansion@^1.1.7: 10 | version "1.1.8" 11 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" 12 | dependencies: 13 | balanced-match "^1.0.0" 14 | concat-map "0.0.1" 15 | 16 | browser-stdout@1.3.0: 17 | version "1.3.0" 18 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" 19 | 20 | commander@2.9.0: 21 | version "2.9.0" 22 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" 23 | dependencies: 24 | graceful-readlink ">= 1.0.0" 25 | 26 | concat-map@0.0.1: 27 | version "0.0.1" 28 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 29 | 30 | concat-stream@^1.4.7: 31 | version "1.6.0" 32 | resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" 33 | dependencies: 34 | inherits "^2.0.3" 35 | readable-stream "^2.2.2" 36 | typedarray "^0.0.6" 37 | 38 | core-util-is@~1.0.0: 39 | version "1.0.2" 40 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 41 | 42 | cross-spawn@^5.0.1: 43 | version "5.1.0" 44 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" 45 | dependencies: 46 | lru-cache "^4.0.1" 47 | shebang-command "^1.2.0" 48 | which "^1.2.9" 49 | 50 | debug@2.6.8: 51 | version "2.6.8" 52 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" 53 | dependencies: 54 | ms "2.0.0" 55 | 56 | diff@3.2.0: 57 | version "3.2.0" 58 | resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" 59 | 60 | escape-string-regexp@1.0.5: 61 | version "1.0.5" 62 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 63 | 64 | fs.realpath@^1.0.0: 65 | version "1.0.0" 66 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 67 | 68 | glob@7.1.1: 69 | version "7.1.1" 70 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" 71 | dependencies: 72 | fs.realpath "^1.0.0" 73 | inflight "^1.0.4" 74 | inherits "2" 75 | minimatch "^3.0.2" 76 | once "^1.3.0" 77 | path-is-absolute "^1.0.0" 78 | 79 | "graceful-readlink@>= 1.0.0": 80 | version "1.0.1" 81 | resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" 82 | 83 | growl@1.9.2: 84 | version "1.9.2" 85 | resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" 86 | 87 | has-flag@^1.0.0: 88 | version "1.0.0" 89 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" 90 | 91 | inflight@^1.0.4: 92 | version "1.0.6" 93 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 94 | dependencies: 95 | once "^1.3.0" 96 | wrappy "1" 97 | 98 | inherits@2, inherits@^2.0.3, inherits@~2.0.3: 99 | version "2.0.3" 100 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 101 | 102 | isarray@~1.0.0: 103 | version "1.0.0" 104 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 105 | 106 | isexe@^2.0.0: 107 | version "2.0.0" 108 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 109 | 110 | json3@3.3.2: 111 | version "3.3.2" 112 | resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" 113 | 114 | lodash._baseassign@^3.0.0: 115 | version "3.2.0" 116 | resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" 117 | dependencies: 118 | lodash._basecopy "^3.0.0" 119 | lodash.keys "^3.0.0" 120 | 121 | lodash._basecopy@^3.0.0: 122 | version "3.0.1" 123 | resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" 124 | 125 | lodash._basecreate@^3.0.0: 126 | version "3.0.3" 127 | resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz#1bc661614daa7fc311b7d03bf16806a0213cf821" 128 | 129 | lodash._getnative@^3.0.0: 130 | version "3.9.1" 131 | resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" 132 | 133 | lodash._isiterateecall@^3.0.0: 134 | version "3.0.9" 135 | resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" 136 | 137 | lodash.create@3.1.1: 138 | version "3.1.1" 139 | resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7" 140 | dependencies: 141 | lodash._baseassign "^3.0.0" 142 | lodash._basecreate "^3.0.0" 143 | lodash._isiterateecall "^3.0.0" 144 | 145 | lodash.isarguments@^3.0.0: 146 | version "3.1.0" 147 | resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" 148 | 149 | lodash.isarray@^3.0.0: 150 | version "3.0.4" 151 | resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" 152 | 153 | lodash.keys@^3.0.0: 154 | version "3.1.2" 155 | resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" 156 | dependencies: 157 | lodash._getnative "^3.0.0" 158 | lodash.isarguments "^3.0.0" 159 | lodash.isarray "^3.0.0" 160 | 161 | lru-cache@^4.0.1: 162 | version "4.1.1" 163 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" 164 | dependencies: 165 | pseudomap "^1.0.2" 166 | yallist "^2.1.2" 167 | 168 | minimatch@^3.0.2: 169 | version "3.0.4" 170 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 171 | dependencies: 172 | brace-expansion "^1.1.7" 173 | 174 | minimist@0.0.8: 175 | version "0.0.8" 176 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 177 | 178 | mkdirp@0.5.1: 179 | version "0.5.1" 180 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 181 | dependencies: 182 | minimist "0.0.8" 183 | 184 | mocha@^3.5.0: 185 | version "3.5.0" 186 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-3.5.0.tgz#1328567d2717f997030f8006234bce9b8cd72465" 187 | dependencies: 188 | browser-stdout "1.3.0" 189 | commander "2.9.0" 190 | debug "2.6.8" 191 | diff "3.2.0" 192 | escape-string-regexp "1.0.5" 193 | glob "7.1.1" 194 | growl "1.9.2" 195 | json3 "3.3.2" 196 | lodash.create "3.1.1" 197 | mkdirp "0.5.1" 198 | supports-color "3.1.2" 199 | 200 | ms@2.0.0: 201 | version "2.0.0" 202 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 203 | 204 | once@^1.3.0: 205 | version "1.4.0" 206 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 207 | dependencies: 208 | wrappy "1" 209 | 210 | os-shim@^0.1.2: 211 | version "0.1.3" 212 | resolved "https://registry.yarnpkg.com/os-shim/-/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917" 213 | 214 | path-is-absolute@^1.0.0: 215 | version "1.0.1" 216 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 217 | 218 | pre-commit@^1.0.5: 219 | version "1.2.2" 220 | resolved "https://registry.yarnpkg.com/pre-commit/-/pre-commit-1.2.2.tgz#dbcee0ee9de7235e57f79c56d7ce94641a69eec6" 221 | dependencies: 222 | cross-spawn "^5.0.1" 223 | spawn-sync "^1.0.15" 224 | which "1.2.x" 225 | 226 | process-nextick-args@~1.0.6: 227 | version "1.0.7" 228 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" 229 | 230 | pseudomap@^1.0.2: 231 | version "1.0.2" 232 | resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" 233 | 234 | readable-stream@^2.2.2: 235 | version "2.3.3" 236 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" 237 | dependencies: 238 | core-util-is "~1.0.0" 239 | inherits "~2.0.3" 240 | isarray "~1.0.0" 241 | process-nextick-args "~1.0.6" 242 | safe-buffer "~5.1.1" 243 | string_decoder "~1.0.3" 244 | util-deprecate "~1.0.1" 245 | 246 | safe-buffer@~5.1.0, safe-buffer@~5.1.1: 247 | version "5.1.1" 248 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" 249 | 250 | shebang-command@^1.2.0: 251 | version "1.2.0" 252 | resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" 253 | dependencies: 254 | shebang-regex "^1.0.0" 255 | 256 | shebang-regex@^1.0.0: 257 | version "1.0.0" 258 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" 259 | 260 | should-equal@^2.0.0: 261 | version "2.0.0" 262 | resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3" 263 | dependencies: 264 | should-type "^1.4.0" 265 | 266 | should-format@^3.0.3: 267 | version "3.0.3" 268 | resolved "https://registry.yarnpkg.com/should-format/-/should-format-3.0.3.tgz#9bfc8f74fa39205c53d38c34d717303e277124f1" 269 | dependencies: 270 | should-type "^1.3.0" 271 | should-type-adaptors "^1.0.1" 272 | 273 | should-type-adaptors@^1.0.1: 274 | version "1.0.1" 275 | resolved "https://registry.yarnpkg.com/should-type-adaptors/-/should-type-adaptors-1.0.1.tgz#efe5553cdf68cff66e5c5f51b712dc351c77beaa" 276 | dependencies: 277 | should-type "^1.3.0" 278 | should-util "^1.0.0" 279 | 280 | should-type@^1.3.0, should-type@^1.4.0: 281 | version "1.4.0" 282 | resolved "https://registry.yarnpkg.com/should-type/-/should-type-1.4.0.tgz#0756d8ce846dfd09843a6947719dfa0d4cff5cf3" 283 | 284 | should-util@^1.0.0: 285 | version "1.0.0" 286 | resolved "https://registry.yarnpkg.com/should-util/-/should-util-1.0.0.tgz#c98cda374aa6b190df8ba87c9889c2b4db620063" 287 | 288 | should@^12.0.0: 289 | version "12.0.0" 290 | resolved "https://registry.yarnpkg.com/should/-/should-12.0.0.tgz#846667cd241691a6509fff0820f44d39143ddcf7" 291 | dependencies: 292 | should-equal "^2.0.0" 293 | should-format "^3.0.3" 294 | should-type "^1.4.0" 295 | should-type-adaptors "^1.0.1" 296 | should-util "^1.0.0" 297 | 298 | spawn-sync@^1.0.15: 299 | version "1.0.15" 300 | resolved "https://registry.yarnpkg.com/spawn-sync/-/spawn-sync-1.0.15.tgz#b00799557eb7fb0c8376c29d44e8a1ea67e57476" 301 | dependencies: 302 | concat-stream "^1.4.7" 303 | os-shim "^0.1.2" 304 | 305 | string_decoder@~1.0.3: 306 | version "1.0.3" 307 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" 308 | dependencies: 309 | safe-buffer "~5.1.0" 310 | 311 | supports-color@3.1.2: 312 | version "3.1.2" 313 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" 314 | dependencies: 315 | has-flag "^1.0.0" 316 | 317 | typedarray@^0.0.6: 318 | version "0.0.6" 319 | resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" 320 | 321 | util-deprecate@~1.0.1: 322 | version "1.0.2" 323 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 324 | 325 | which@1.2.x: 326 | version "1.2.14" 327 | resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" 328 | dependencies: 329 | isexe "^2.0.0" 330 | 331 | which@^1.2.9: 332 | version "1.3.0" 333 | resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" 334 | dependencies: 335 | isexe "^2.0.0" 336 | 337 | wrappy@1: 338 | version "1.0.2" 339 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 340 | 341 | yallist@^2.1.2: 342 | version "2.1.2" 343 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" 344 | --------------------------------------------------------------------------------