├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bhdr.js ├── example.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # 0x 40 | profile-* 41 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "4" 5 | - "6" 6 | - "7" 7 | env: 8 | - CC=gcc-4.8 CXX=g++-4.8 9 | addons: 10 | apt: 11 | sources: 12 | - ubuntu-toolchain-r-test 13 | packages: 14 | - gcc-4.8 15 | - g++-4.8 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Matteo Collina 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 | # bhdr  [![Build Status](https://travis-ci.org/mcollina/bhdr.svg)](https://travis-ci.org/mcollina/bhdr) 2 | 3 | Benchmark utility powered by [hdr 4 | histograms](https://github.com/mcollina/native-hdr-histogram), for node. 5 | 6 | It is API-compatible with [fastbench](https://github.com/mcollina/fastbench). 7 | 8 | ## Install 9 | 10 | ```js 11 | npm install bhdr 12 | ``` 13 | 14 | ## Usage 15 | 16 | ```js 17 | 'use strict' 18 | 19 | var bench = require('bhdr') 20 | 21 | var run = bench([ 22 | function benchSetTimeout (done) { 23 | setTimeout(done, 0) 24 | }, 25 | function benchSetImmediate (done) { 26 | setImmediate(done) 27 | }, 28 | function benchNextTick (done) { 29 | process.nextTick(done) 30 | } 31 | ], 1000) 32 | 33 | // run them two times 34 | run(run) 35 | ``` 36 | 37 | Output 38 | 39 | ``` 40 | $ node example.js 41 | benchSetTimeout: 1 ops/ms +-0.16 42 | benchSetImmediate: too fast to measure 43 | benchNextTick: too fast to measure 44 | benchSetTimeout: 1 ops/ms +-0.11 45 | benchSetImmediate: too fast to measure 46 | benchNextTick: too fast to measure 47 | ``` 48 | 49 | You can disable colors by passing a `--no-color` flag to your node 50 | script. 51 | 52 | ## API 53 | 54 | ### bench(functions, iterations) 55 | 56 | Build a benchmark for the given functions and that precise number of 57 | iterations. It returns a function to run the benchmark. 58 | 59 | The iterations parameter can also be an `Object`, in which case it 60 | acceps two options: 61 | 62 | * `iterations`: the number of iterations (required) 63 | * `max`: is a an alias for iterations 64 | * `color`: if the output should have color (default: true) 65 | 66 | ## Acknowledgements 67 | 68 | This project is kindly sponsored by [nearForm](http://nearform.com). 69 | 70 | ## License 71 | 72 | MIT 73 | -------------------------------------------------------------------------------- /bhdr.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Histogram = require('native-hdr-histogram') 4 | const histutils = require('hdr-histogram-percentiles-obj') 5 | const chalk = require('chalk') 6 | const colors = ['red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'gray'] 7 | const console = require('console') // for proxyquire in tests 8 | const process = require('process') // for proxyquire in tests 9 | 10 | function build (funcs, opts) { 11 | var maxRuns 12 | 13 | if (!Array.isArray(funcs)) { 14 | funcs = [funcs] 15 | } 16 | 17 | if (typeof opts === 'object') { 18 | maxRuns = opts.max || opts.iterations 19 | } else { 20 | maxRuns = opts 21 | } 22 | 23 | var ctx = new chalk.constructor({ 24 | enabled: opts.color !== false 25 | }) 26 | 27 | return run 28 | 29 | function run (done) { 30 | done = done || noop 31 | var results = [] 32 | var toExecs = [].concat(funcs) 33 | var currentColor = 0 34 | var print = noop 35 | 36 | if (done.length < 2) { 37 | print = process.env.BHDR_JSON ? jsonPrint : consolePrint 38 | } 39 | 40 | var start = process.hrtime() 41 | runFunc() 42 | 43 | function runFunc () { 44 | const histogram = new Histogram(1, 1000000, 5) 45 | var runs = 0 46 | var errors = 0 47 | var func = toExecs.shift() 48 | 49 | if (!func) { 50 | done(null, { 51 | results, 52 | totalTime: asMs(process.hrtime(start)) 53 | }) 54 | return 55 | } 56 | 57 | var time = process.hrtime() 58 | func(next) 59 | 60 | function next (err) { 61 | time = process.hrtime(time) 62 | const ms = asMs(time) 63 | histogram.record(ms) 64 | 65 | if (err) { 66 | errors++ 67 | } 68 | 69 | if (++runs < maxRuns) { 70 | time = process.hrtime() 71 | func(next) 72 | } else { 73 | const res = buildResult(histogram, func, errors, runs) 74 | print(res) 75 | results.push(res) 76 | runFunc() 77 | } 78 | } 79 | } 80 | 81 | function buildResult (histogram, func, errors, runs) { 82 | const result = histutils.histAsObj(histogram) 83 | result.name = func.name 84 | result.runs = maxRuns - (maxRuns - runs) 85 | result.errors = errors 86 | histutils.addPercentiles(histogram, result) 87 | 88 | return result 89 | } 90 | 91 | function nextColor () { 92 | if (currentColor === colors.length) { 93 | currentColor = 0 94 | } 95 | return colors[currentColor++] 96 | } 97 | 98 | function consolePrint (result) { 99 | const color = nextColor() 100 | if (result.errors) { 101 | console.log(ctx.bold(chalk[color](result.name + ': ' + result.errors + ' errors'))) 102 | } else if (result.mean === 0) { 103 | console.log(ctx[color](result.name + ': too fast to measure')) 104 | } else { 105 | console.log(ctx[color](result.name + ': ' + result.mean + ' ops/ms +-' + result.stddev)) 106 | } 107 | } 108 | 109 | function jsonPrint (result) { 110 | console.log(JSON.stringify(result)) 111 | } 112 | } 113 | } 114 | 115 | function asMs (time) { 116 | return time[0] * 1e3 + time[1] / 1e6 117 | } 118 | 119 | function noop () {} 120 | 121 | module.exports = build 122 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var bench = require('./') 4 | 5 | var run = bench([ 6 | function benchSetTimeout (done) { 7 | setTimeout(done, 0) 8 | }, 9 | function benchSetImmediate (done) { 10 | setImmediate(done) 11 | }, 12 | function benchNextTick (done) { 13 | process.nextTick(done) 14 | } 15 | ], 1000) 16 | 17 | run(run) 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bhdr", 3 | "version": "0.0.1", 4 | "description": "benchmark utility powered by hdr histograms", 5 | "main": "bhdr.js", 6 | "scripts": { 7 | "test": "standard | snazzy && tap test.js" 8 | }, 9 | "precommit": "test", 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/mcollina/bhdr.git" 13 | }, 14 | "keywords": [ 15 | "hdr", 16 | "histogram", 17 | "benchmark", 18 | "runner" 19 | ], 20 | "author": "Matteo Collina ", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/mcollina/bhdr/issues" 24 | }, 25 | "homepage": "https://github.com/mcollina/bhdr#readme", 26 | "devDependencies": { 27 | "pre-commit": "^1.1.3", 28 | "snazzy": "^5.0.0", 29 | "standard": "^8.5.0", 30 | "tap": "^8.0.0" 31 | }, 32 | "dependencies": { 33 | "chalk": "^1.1.3", 34 | "fastq": "^1.4.1", 35 | "hdr-histogram-percentiles-obj": "^1.1.0", 36 | "native-hdr-histogram": "^0.4.1", 37 | "proxyquire": "^1.7.10", 38 | "reusify": "^1.0.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const test = require('tap').test 4 | const chalk = require('chalk') 5 | const proxyquire = require('proxyquire') 6 | const build = require('.') 7 | 8 | var loopCalled = 0 9 | function loop (done) { 10 | loopCalled++ 11 | for (var i = 0; i < 1000000; i++) {} 12 | process.nextTick(done) 13 | } 14 | 15 | test('get basic data', function (t) { 16 | const run = build(loop, 1000) 17 | 18 | run(function (err, data) { 19 | var result = data.results 20 | t.error(err) 21 | t.ok(data.totalTime, 'there is a total time') 22 | t.equal(result.length, 1, 'number of result') 23 | t.equal(loopCalled, 1000, 'func called num times') 24 | t.equal(result[0].name, 'loop', 'name is set') 25 | t.equal(result[0].runs, 1000, 'runs are set') 26 | t.equal(result[0].errors, 0, 'no errors') 27 | t.ok(result[0].average >= 0, 'average exists') 28 | t.ok(result[0].stddev >= 0, 'stddev exists') 29 | t.ok(result[0].min >= 0, 'min exists') 30 | t.ok(result[0].max >= 0, 'max exists') 31 | t.end() 32 | }) 33 | }) 34 | 35 | test('get basic data with options.iterations', function (t) { 36 | const run = build(loop, { iterations: 100 }) 37 | 38 | run(function (err, data) { 39 | var result = data.results 40 | t.error(err) 41 | t.ok(data.totalTime, 'there is a total time') 42 | t.equal(result.length, 1, 'number of result') 43 | t.equal(result[0].name, 'loop', 'name is set') 44 | t.equal(result[0].runs, 100, 'runs are set') 45 | t.equal(result[0].errors, 0, 'no errors') 46 | t.ok(result[0].average >= 0, 'average exists') 47 | t.ok(result[0].stddev >= 0, 'stddev exists') 48 | t.ok(result[0].min >= 0, 'min exists') 49 | t.ok(result[0].max >= 0, 'max exists') 50 | t.end() 51 | }) 52 | }) 53 | 54 | test('get basic data with options.max', function (t) { 55 | const run = build(loop, { max: 100 }) 56 | 57 | run(function (err, data) { 58 | var result = data.results 59 | t.error(err) 60 | t.ok(data.totalTime, 'there is a total time') 61 | t.equal(result.length, 1, 'number of result') 62 | t.equal(result[0].name, 'loop', 'name is set') 63 | t.equal(result[0].runs, 100, 'runs are set') 64 | t.equal(result[0].errors, 0, 'no errors') 65 | t.ok(result[0].average >= 0, 'average exists') 66 | t.ok(result[0].stddev >= 0, 'stddev exists') 67 | t.ok(result[0].min >= 0, 'min exists') 68 | t.ok(result[0].max >= 0, 'max exists') 69 | t.end() 70 | }) 71 | }) 72 | 73 | test('array support', function (t) { 74 | const run = build([ 75 | loop, 76 | function loop2 (done) { 77 | for (var i = 0; i < 1000000; i++) {} 78 | process.nextTick(done) 79 | } 80 | ], 1000) 81 | 82 | run(function (err, data) { 83 | t.error(err) 84 | t.ok(data.totalTime, 'there is a total time') 85 | var result = data.results 86 | t.equal(result.length, 2, 'number of result') 87 | t.equal(result[0].name, 'loop', 'name is set') 88 | t.equal(result[1].name, 'loop2', 'name is set') 89 | for (var i = 0; i < result.length; i++) { 90 | t.equal(result[i].runs, 1000, 'runs are set') 91 | t.equal(result[i].errors, 0, 'no errors') 92 | t.ok(result[i].average >= 0, 'average exists') 93 | t.ok(result[i].stddev >= 0, 'stddev exists') 94 | t.ok(result[i].min >= 0, 'min exists') 95 | t.ok(result[i].max >= 0, 'max exists') 96 | } 97 | t.end() 98 | }) 99 | }) 100 | 101 | test('writes to stdout with color if not callback', function (t) { 102 | t.plan(1) 103 | 104 | var chalkEnabled = chalk.enabled 105 | chalk.enabled = true 106 | 107 | var bench = proxyquire('./', { 108 | console: { 109 | log: function (key) { 110 | t.ok(chalk.hasColor(key), 'has color') 111 | } 112 | } 113 | }) 114 | 115 | var run = bench([ 116 | loop 117 | ], 42) 118 | 119 | run(function () { 120 | chalk.enabled = chalkEnabled 121 | }) 122 | }) 123 | 124 | test('does not write to stdout if callback with 2 args', function (t) { 125 | var bench = proxyquire('./', { 126 | console: { 127 | log: function () { 128 | t.fail('no console log') 129 | } 130 | } 131 | }) 132 | 133 | var run = bench([ 134 | loop 135 | ], 42) 136 | 137 | run(function (err, result) { 138 | t.error(err) 139 | t.end() 140 | }) 141 | }) 142 | 143 | test('does write newline delimited JSON if process.env.BHDR_JSON is set', function (t) { 144 | t.plan(9) 145 | 146 | var bench = proxyquire('./', { 147 | process: { 148 | env: { 149 | BHDR_JSON: 'true' 150 | } 151 | }, 152 | console: { 153 | log: function (str) { 154 | t.equal(arguments.length, 1) 155 | t.ok(typeof str === 'string') 156 | var result = JSON.parse(str) 157 | t.equal(result.name, 'loop', 'name is set') 158 | t.equal(result.runs, 1000, 'runs are set') 159 | t.equal(result.errors, 0, 'no errors') 160 | t.ok(result.average >= 0, 'average exists') 161 | t.ok(result.stddev >= 0, 'stddev exists') 162 | t.ok(result.min >= 0, 'min exists') 163 | t.ok(result.max >= 0, 'max exists') 164 | } 165 | } 166 | }) 167 | 168 | var run = bench([ 169 | loop 170 | ], 1000) 171 | 172 | run() 173 | }) 174 | 175 | test('disable color', function (t) { 176 | t.plan(1) 177 | 178 | var bench = proxyquire('./', { 179 | console: { 180 | log: function (key) { 181 | t.notOk(chalk.hasColor(key), 'has no color') 182 | } 183 | } 184 | }) 185 | 186 | var run = bench([ 187 | function first (done) { 188 | setImmediate(done) 189 | } 190 | ], { 191 | iterations: 42, 192 | color: false 193 | }) 194 | 195 | run() 196 | }) 197 | --------------------------------------------------------------------------------