├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '9' 4 | - '8' 5 | - '6' 6 | - '4' 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Thomas Watson Steen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # frequency-counter 2 | 3 | Count the number of occurrences of a repeating event per unit of time. 4 | 5 | [![Build status](https://travis-ci.org/watson/frequency-counter.svg?branch=master)](https://travis-ci.org/watson/frequency-counter) 6 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/feross/standard) 7 | 8 | ## Installation 9 | 10 | ``` 11 | npm install frequency-counter --save 12 | ``` 13 | 14 | ## Usage example 15 | 16 | Example HTTP server responding with the number of requests per minute (rpm): 17 | 18 | ```js 19 | const http = require('http') 20 | const Freq = require('frequency-counter') 21 | 22 | // if no arguments are given a window of one minute is used 23 | const counter = new Freq() 24 | 25 | const server = http.createServer(function (req, res) { 26 | // increment the count 27 | counter.inc() 28 | 29 | // return current frequency to client 30 | res.end('rpm: ' + counter.freq() + '\n') 31 | }) 32 | 33 | server.listen(3000) 34 | ``` 35 | 36 | ## API 37 | 38 | ### `counter = new Freq([windowSize])` 39 | 40 | Initialize the frequency counter. Optionally set a custom window size in 41 | seconds over which the frequency should be calcuated (default: `60`). 42 | 43 | ### `counter.inc([amount])` 44 | 45 | Track an occurrence by incrementing a counter. Optionally provide the 46 | `amount` to increment by (default: `1`). 47 | 48 | ### `number = counter.freq()` 49 | 50 | Calculate and return the current frequency. 51 | 52 | ## License 53 | 54 | MIT 55 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = FrequencyCounter 4 | 5 | function FrequencyCounter (windowSize) { 6 | if (!(this instanceof FrequencyCounter)) return new FrequencyCounter(windowSize) 7 | 8 | this._windowSize = windowSize || 60 // default to a window of 60 seconds (rpm) 9 | this._window = new Uint32Array(this._windowSize).fill(0) 10 | this._lastInc = epoch() 11 | } 12 | 13 | FrequencyCounter.prototype.inc = function (n) { 14 | const now = epoch() 15 | const index = now % this._windowSize 16 | this._clearGaps(index, now) 17 | this._window[index] += n || 1 18 | this._lastInc = now 19 | } 20 | 21 | FrequencyCounter.prototype.freq = function () { 22 | const now = epoch() 23 | const index = now % this._windowSize 24 | this._clearGaps(index, now) 25 | return this._window.reduce(sum) 26 | } 27 | 28 | // Set all buckets between the last index and the current index to zero 29 | // (including the current index, excluding the last index) 30 | FrequencyCounter.prototype._clearGaps = function (currentIndex, now) { 31 | let gaps = Math.min(now - this._lastInc, this._windowSize) 32 | while (gaps-- > 0) { 33 | const offset = (currentIndex - gaps) % this._windowSize 34 | const index = offset < 0 ? this._windowSize + offset : offset 35 | this._window[index] = 0 36 | } 37 | } 38 | 39 | function sum (a, b) { 40 | return a + b 41 | } 42 | 43 | function epoch () { 44 | return process.hrtime()[0] 45 | } 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frequency-counter", 3 | "version": "1.0.1", 4 | "description": "Count the number of occurrences of a repeating event per unit of time", 5 | "main": "index.js", 6 | "dependencies": {}, 7 | "devDependencies": { 8 | "standard": "^11.0.1", 9 | "tape": "^4.9.0" 10 | }, 11 | "scripts": { 12 | "test": "standard && node test.js" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/watson/frequency-counter.git" 17 | }, 18 | "keywords": [ 19 | "freq", 20 | "frequency", 21 | "freqency", 22 | "calculate", 23 | "calc", 24 | "stat", 25 | "stats", 26 | "rpm" 27 | ], 28 | "author": "Thomas Watson (https://twitter.com/wa7son)", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/watson/frequency-counter/issues" 32 | }, 33 | "homepage": "https://github.com/watson/frequency-counter#readme", 34 | "coordinates": [ 35 | 55.777623, 36 | 12.589892 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const test = require('tape') 4 | const Freq = require('./') 5 | 6 | test('no incs', function (t) { 7 | const counter = Freq() 8 | t.equal(counter.freq(), 0) 9 | t.end() 10 | }) 11 | 12 | test('one inc', function (t) { 13 | const counter = Freq() 14 | counter.inc() 15 | t.equal(counter.freq(), 1) 16 | t.end() 17 | }) 18 | 19 | test('custom inc', function (t) { 20 | const counter = Freq() 21 | counter.inc(42) 22 | t.equal(counter.freq(), 42) 23 | t.end() 24 | }) 25 | 26 | test('multiple incs', function (t) { 27 | const counter = Freq() 28 | for (let n = 0; n < 100; n++) counter.inc() 29 | t.equal(counter.freq(), 100) 30 | t.end() 31 | }) 32 | 33 | test('don\'t count old data', function (t) { 34 | stopTime() 35 | const counter = Freq() 36 | let i = 1 37 | 38 | for (; i < 60 * 2; i++) { 39 | counter.inc() 40 | const expected = Math.min(i, 60) 41 | t.equal(counter.freq(), expected, 'after ' + i + ' seconds, the frequency should be ' + expected) 42 | incTime(1) 43 | } 44 | 45 | releaseTime() 46 | t.end() 47 | }) 48 | 49 | test('don\'t count old data, custom window', function (t) { 50 | stopTime() 51 | const counter = Freq(5) 52 | let i = 1 53 | 54 | for (; i < 5 * 2; i++) { 55 | counter.inc() 56 | const expected = Math.min(i, 5) 57 | t.equal(counter.freq(), expected, 'after ' + i + ' seconds, the frequency should be ' + expected) 58 | incTime(1) 59 | } 60 | 61 | releaseTime() 62 | t.end() 63 | }) 64 | 65 | const origHrtimeFn = process.hrtime 66 | let stoppedTime 67 | 68 | function stopTime () { 69 | stoppedTime = process.hrtime() 70 | process.hrtime = function () { 71 | return stoppedTime 72 | } 73 | } 74 | 75 | function releaseTime () { 76 | process.hrtime = origHrtimeFn 77 | } 78 | 79 | function incTime (s) { 80 | stoppedTime[0] += s 81 | } 82 | --------------------------------------------------------------------------------