Skip to content

Commit 8151240

Browse files
authored
Benchmark task (#1251)
This adds a benchmark task inspired by #1167 and #1163. Invoke with `yarn benchmark`. Allows filtering down which benchmark to run, ala jest, and supplying which revisions to run against.
1 parent 86d33b4 commit 8151240

File tree

5 files changed

+186
-0
lines changed

5 files changed

+186
-0
lines changed

.npmignore

+1
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ coverage
1616
resources
1717
src
1818
dist
19+
__tests__
1920
npm

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"testonly:cover": "babel-node ./node_modules/.bin/isparta cover --root src --report html _mocha -- $npm_package_options_mocha",
2626
"testonly:coveralls": "babel-node ./node_modules/.bin/isparta cover --root src --report lcovonly _mocha -- $npm_package_options_mocha && cat ./coverage/lcov.info | coveralls",
2727
"lint": "eslint --rulesdir ./resources/lint src || (printf '\\033[33mTry: \\033[7m npm run lint -- --fix \\033[0m\\n' && exit 1)",
28+
"benchmark": "node ./resources/benchmark.js",
2829
"prettier": "prettier --write 'src/**/*.js'",
2930
"check": "flow check",
3031
"check-cover": "for file in {src/*.js,src/**/*.js}; do echo $file; flow coverage $file; done",
@@ -52,6 +53,8 @@
5253
"babel-plugin-transform-flow-strip-types": "6.22.0",
5354
"babel-plugin-transform-object-rest-spread": "6.26.0",
5455
"babel-preset-env": "^1.5.2",
56+
"beautify-benchmark": "0.2.4",
57+
"benchmark": "2.1.4",
5558
"chai": "4.1.2",
5659
"chai-json-equal": "0.0.1",
5760
"chai-spies-next": "0.9.3",

resources/benchmark.js

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
const { Suite } = require('benchmark');
9+
const beautifyBenchmark = require('beautify-benchmark');
10+
const { execSync } = require('child_process');
11+
const os = require('os');
12+
const path = require('path');
13+
14+
// Like build:cjs, but includes __tests__ and copies other files.
15+
const BUILD_CMD = 'babel src --optional runtime --copy-files --out-dir dist/';
16+
const LOCAL = 'local';
17+
const LOCAL_DIR = path.join(__dirname, '../');
18+
const TEMP_DIR = os.tmpdir();
19+
20+
// Returns the complete git hash for a given git revision reference.
21+
function hashForRevision(revision) {
22+
if (revision === LOCAL) {
23+
return revision;
24+
}
25+
const out = execSync(`git rev-parse "${revision}"`, { encoding: 'utf8' });
26+
const match = /[0-9a-f]{8,40}/.exec(out);
27+
if (!match) {
28+
throw new Error(`Bad results for revision ${revision}: ${out}`);
29+
}
30+
return match[0];
31+
}
32+
33+
// Returns the temporary directory which hosts the files for this git hash.
34+
function dirForHash(hash) {
35+
if (hash === LOCAL) {
36+
return path.join(__dirname, '../');
37+
}
38+
return path.join(TEMP_DIR, 'graphql-js-benchmark', hash);
39+
}
40+
41+
// Build a benchmarkable environment for the given revision.
42+
function prepareRevision(revision) {
43+
console.log(`🍳 Preparing ${revision}...`);
44+
const hash = hashForRevision(revision);
45+
const dir = dirForHash(hash);
46+
if (hash === LOCAL) {
47+
execSync(`(cd "${dir}" && yarn run ${BUILD_CMD})`);
48+
} else {
49+
execSync(`
50+
if [ ! -d "${dir}" ]; then
51+
mkdir -p "${dir}" &&
52+
git archive "${hash}" | tar -xC "${dir}" &&
53+
(cd "${dir}" && yarn install);
54+
fi &&
55+
# Copy in local tests so the same logic applies to each revision.
56+
for file in $(cd "${LOCAL_DIR}src"; find . -path '*/__tests__/*.js');
57+
do cp "${LOCAL_DIR}src/$file" "${dir}/src/$file";
58+
done &&
59+
(cd "${dir}" && yarn run ${BUILD_CMD})
60+
`);
61+
}
62+
}
63+
64+
// Find all benchmark tests to be run.
65+
function findBenchmarks() {
66+
const out = execSync(
67+
`(cd ${LOCAL_DIR}src; find . -path '*/__tests__/*-benchmark.js')`,
68+
{ encoding: 'utf8' },
69+
);
70+
return out.split('\n').filter(Boolean);
71+
}
72+
73+
// Run a given benchmark test with the provided revisions.
74+
function runBenchmark(benchmark, revisions) {
75+
const modules = revisions.map(revision =>
76+
require(path.join(
77+
dirForHash(hashForRevision(revision)),
78+
'dist',
79+
benchmark,
80+
)),
81+
);
82+
const suite = new Suite(modules[0].name, {
83+
onStart(event) {
84+
console.log('⏱️ ' + event.currentTarget.name);
85+
},
86+
onCycle(event) {
87+
beautifyBenchmark.add(event.target);
88+
},
89+
onComplete() {
90+
beautifyBenchmark.log();
91+
},
92+
});
93+
for (let i = 0; i < revisions.length; i++) {
94+
suite.add(revisions[i], modules[i].measure);
95+
}
96+
suite.run();
97+
}
98+
99+
// Prepare all revisions and run benchmarks matching a pattern against them.
100+
function prepareAndRunBenchmarks(benchmarkPatterns, revisions) {
101+
const benchmarks = findBenchmarks().filter(
102+
benchmark =>
103+
benchmarkPatterns.length === 0 ||
104+
benchmarkPatterns.some(pattern => benchmark.indexOf(pattern) !== -1),
105+
);
106+
if (benchmarks.length === 0) {
107+
console.warn(
108+
'No benchmarks matching: ' +
109+
`\u001b[1m${benchmarkPatterns.join('\u001b[0m or \u001b[1m')}\u001b[0m`,
110+
);
111+
return;
112+
}
113+
revisions.forEach(revision => prepareRevision(revision));
114+
benchmarks.forEach(benchmark => runBenchmark(benchmark, revisions));
115+
}
116+
117+
function getArguments(argv) {
118+
const revsIdx = argv.indexOf('--revs');
119+
const revsArgs = revsIdx === -1 ? [] : argv.slice(revsIdx + 1);
120+
const benchmarkPatterns = revsIdx === -1 ? argv : argv.slice(0, revsIdx);
121+
let assumeArgs;
122+
let revisions;
123+
switch (revsArgs.length) {
124+
case 0:
125+
assumeArgs = [...benchmarkPatterns, '--revs', 'local', 'HEAD'];
126+
revisions = [LOCAL, 'HEAD'];
127+
break;
128+
case 1:
129+
assumeArgs = [...benchmarkPatterns, '--revs', 'local', revsArgs[0]];
130+
revisions = [LOCAL, revsArgs[0]];
131+
break;
132+
default:
133+
revisions = revsArgs;
134+
break;
135+
}
136+
if (assumeArgs) {
137+
console.warn(
138+
`Assuming you meant: \u001b[1mbenchmark ${assumeArgs.join(' ')}\u001b[0m`,
139+
);
140+
}
141+
return { benchmarkPatterns, revisions };
142+
}
143+
144+
// Get the revisions and make things happen!
145+
if (require.main === module) {
146+
const { benchmarkPatterns, revisions } = getArguments(process.argv.slice(2));
147+
prepareAndRunBenchmarks(benchmarkPatterns, revisions);
148+
}
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import { join } from 'path';
9+
import { readFileSync } from 'fs';
10+
import { parse } from '../parser';
11+
12+
const kitchenSink = readFileSync(join(__dirname, '/kitchen-sink.graphql'), {
13+
encoding: 'utf8',
14+
});
15+
16+
export const name = 'Parse kitchen sink';
17+
export function measure() {
18+
parse(kitchenSink);
19+
}

yarn.lock

+15
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,17 @@ bcrypt-pbkdf@^1.0.0:
786786
dependencies:
787787
tweetnacl "^0.14.3"
788788

789+
790+
version "0.2.4"
791+
resolved "https://registry.yarnpkg.com/beautify-benchmark/-/beautify-benchmark-0.2.4.tgz#3151def14c1a2e0d07ff2e476861c7ed0e1ae39b"
792+
793+
794+
version "2.1.4"
795+
resolved "https://registry.yarnpkg.com/benchmark/-/benchmark-2.1.4.tgz#09f3de31c916425d498cc2ee565a0ebf3c2a5629"
796+
dependencies:
797+
lodash "^4.17.4"
798+
platform "^1.3.3"
799+
789800
binary-extensions@^1.0.0:
790801
version "1.11.0"
791802
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205"
@@ -2240,6 +2251,10 @@ pinkie@^2.0.0:
22402251
version "2.0.4"
22412252
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
22422253

2254+
platform@^1.3.3:
2255+
version "1.3.5"
2256+
resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.5.tgz#fb6958c696e07e2918d2eeda0f0bc9448d733444"
2257+
22432258
pluralize@^7.0.0:
22442259
version "7.0.0"
22452260
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"

0 commit comments

Comments
 (0)