Skip to content

Commit

Permalink
Add initial benchmarks
Browse files Browse the repository at this point in the history
Closes #1601.
  • Loading branch information
Marsup committed Nov 3, 2018
1 parent f7a73f5 commit 8adf1d8
Show file tree
Hide file tree
Showing 6 changed files with 327 additions and 0 deletions.
2 changes: 2 additions & 0 deletions benchmarks/.gitignore
@@ -0,0 +1,2 @@
results.json

7 changes: 7 additions & 0 deletions 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.
118 changes: 118 additions & 0 deletions 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();
147 changes: 147 additions & 0 deletions benchmarks/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions 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"
}
}
38 changes: 38 additions & 0 deletions 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);
}
]
];

0 comments on commit 8adf1d8

Please sign in to comment.