diff --git a/benchmarks/.gitignore b/benchmarks/.gitignore new file mode 100644 index 000000000..efdaa9523 --- /dev/null +++ b/benchmarks/.gitignore @@ -0,0 +1,2 @@ +results.json + diff --git a/benchmarks/README.md b/benchmarks/README.md new file mode 100644 index 000000000..23d7c477d --- /dev/null +++ b/benchmarks/README.md @@ -0,0 +1,7 @@ +# Joi benchmarks + +The benchmarks in this folder are there to do performance regression testing. This is not to compare joi to some other library as this is most of the time meaningless. + +Run it first with `npm run bench-update` to establish a baseline then run `npm run bench` or `npm test` to compare your modifications to the baseline. + +Significant (> 10% by default) are put in colors in the report, the rest should be fairly obvious. diff --git a/benchmarks/bench.js b/benchmarks/bench.js new file mode 100644 index 000000000..38af1e1c5 --- /dev/null +++ b/benchmarks/bench.js @@ -0,0 +1,118 @@ +'use strict'; + +const Fs = require('fs'); +const Benchmark = require('benchmark'); +const Bossy = require('bossy'); +const Chalk = require('chalk'); +const CliTable = require('cli-table'); +const D3 = require('d3-format'); + +const definition = { + c: { + alias: 'compare', + type: 'string' + }, + s: { + alias: 'save', + type: 'string' + }, + t: { + alias: 'threshold', + type: 'number', + default: 10 + } +}; + +const args = Bossy.parse(definition); + +let compare; +if (args.compare) { + try { + compare = JSON.parse(Fs.readFileSync(args.compare, 'utf8')); + } + catch (e) { + // Ignore error + } +} + +const formats = { + number: D3.format(',d'), + percentage: D3.format('.2f'), + integer: D3.format(',') +}; + +const Suite = new Benchmark.Suite('joi'); + +const test = ([name, initFn, testFn]) => { + + const [schema, value] = initFn(); + Suite.add(name, () => { + + testFn(schema, value); + }); +}; + +require('./suite').forEach(test); + +Suite + .on('complete', (benches) => { + + const report = benches.currentTarget.map((bench) => { + + const { name, hz, stats } = bench; + return { name, hz, rme: stats.rme, size: stats.sample.length }; + }); + + if (args.save) { + Fs.writeFileSync(args.save, JSON.stringify(report, null, 2), 'utf8'); + } + + const tableDefinition = { + head: [Chalk.blue('Name'), '', Chalk.yellow('Ops/sec'), Chalk.yellow('MoE'), Chalk.yellow('Sample size')], + colAligns: ['left', '', 'right', 'right', 'right'] + }; + + if (compare) { + tableDefinition.head.push('', Chalk.cyan('Previous ops/sec'), Chalk.cyan('Previous MoE'), Chalk.cyan('Previous sample size'), '', Chalk.whiteBright('% difference')); + tableDefinition.colAligns.push('', 'right', 'right', 'right', '', 'right'); + } + + const table = new CliTable(tableDefinition); + + table.push(...report.map((s) => { + + const row = [ + s.name, + '', + formats.number(s.hz), + `± ${formats.percentage(s.rme)} %`, + formats.integer(s.size) + ]; + + if (compare) { + const previousRun = compare.find((run) => run.name === s.name); + if (previousRun) { + const difference = s.hz - previousRun.hz; + const percentage = 100 * difference / previousRun.hz; + const isSignificant = Math.abs(percentage) > args.threshold; + const formattedDifference = `${percentage > 0 ? '+' : ''}${formats.percentage(percentage)} %`; + row.push( + '', + formats.number(previousRun.hz), + `± ${formats.percentage(s.rme)} %`, + formats.integer(s.size), + '', + isSignificant + ? Chalk[difference > 0 ? 'green' : 'red'](formattedDifference) + : formattedDifference + ); + } + } + + return row; + })); + + console.log(table.toString()); + }); + +Suite.run(); diff --git a/benchmarks/package-lock.json b/benchmarks/package-lock.json new file mode 100644 index 000000000..47b3b6ebb --- /dev/null +++ b/benchmarks/package-lock.json @@ -0,0 +1,147 @@ +{ + "name": "benchmarks", + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "benchmark": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz", + "integrity": "sha1-CfPeMckWQl1JjMLuVloOvzwqVik=", + "requires": { + "lodash": "^4.17.4", + "platform": "^1.3.3" + } + }, + "boom": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/boom/-/boom-7.2.2.tgz", + "integrity": "sha512-IFUbOa8PS7xqmhIjpeStwT3d09hGkNYQ6aj2iELSTxcVs2u0aKn1NzhkdUQSzsRg1FVkj3uit3I6mXQCBixw+A==", + "requires": { + "hoek": "6.x.x" + } + }, + "bossy": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/bossy/-/bossy-4.0.3.tgz", + "integrity": "sha512-2Hr2cgtwNi/BWIxwvrr3UbwczPV8gqoHUS8Wzuawo+StFNHDlj/7HGlETh1LX6SqMauBCU8lb+lLBuIFpBNuTA==", + "requires": { + "boom": "7.x.x", + "hoek": "6.x.x", + "joi": "14.x.x" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "cli-table": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", + "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", + "requires": { + "colors": "1.0.3" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" + }, + "d3-format": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.3.2.tgz", + "integrity": "sha512-Z18Dprj96ExragQ0DeGi+SYPQ7pPfRMtUXtsg/ChVIKNBCzjO8XYJvRTC1usblx52lqge56V5ect+frYTQc8WQ==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "hoek": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.0.1.tgz", + "integrity": "sha512-3PvUwBerLNVJiIVQdpkWF9F/M0ekgb2NPJWOhsE28RXSQPsY42YSnaJ8d1kZjcAz58TZ/Fk9Tw64xJsENFlJNw==" + }, + "isemail": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", + "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", + "requires": { + "punycode": "2.x.x" + } + }, + "joi": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/joi/-/joi-14.0.4.tgz", + "integrity": "sha512-KUXRcinDUMMbtlOk7YLGHQvG73dLyf8bmgE+6sBTkdJbZpeGVGAlPXEHLiQBV7KinD/VLD5OA0EUgoTTfbRAJQ==", + "requires": { + "hoek": "6.x.x", + "isemail": "3.x.x", + "topo": "3.x.x" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "platform": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz", + "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "topo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", + "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", + "requires": { + "hoek": "6.x.x" + } + } + } +} diff --git a/benchmarks/package.json b/benchmarks/package.json new file mode 100644 index 000000000..ff7e78976 --- /dev/null +++ b/benchmarks/package.json @@ -0,0 +1,15 @@ +{ + "name": "benchmarks", + "scripts": { + "test": "npm run bench", + "bench": "node ./bench.js --compare results.json", + "bench-update": "npm run bench -- --save results.json" + }, + "dependencies": { + "benchmark": "^2.1.4", + "bossy": "^4.0.3", + "chalk": "^2.4.1", + "cli-table": "^0.3.1", + "d3-format": "^1.3.2" + } +} diff --git a/benchmarks/suite.js b/benchmarks/suite.js new file mode 100644 index 000000000..cb9ee34d5 --- /dev/null +++ b/benchmarks/suite.js @@ -0,0 +1,38 @@ +'use strict'; + +const Joi = require('../'); + +module.exports = [ + [ + 'Simple object', + () => [ + Joi.object({ + id: Joi.string().required(), + level: Joi.string() + .valid(['debug', 'info', 'notice']) + .required() + }).unknown(false), + { id: '1', level: 'info' } + ], + (schema, value) => { + + schema.validate(value, { convert: false }); + } + ], + [ + 'Simple object with inlined options', + () => [ + Joi.object({ + id: Joi.string().required(), + level: Joi.string() + .valid(['debug', 'info', 'notice']) + .required() + }).unknown(false).options({ convert: false }), + { id: '1', level: 'info' } + ], + (schema, value) => { + + schema.validate(value); + } + ] +];