Skip to content

Commit 6073452

Browse files
committed
benchmark: extract benchmarks into separate folder and run them on NPM package
1 parent 3aad20b commit 6073452

26 files changed

+266
-228
lines changed

.eslintignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
/coverage
55
/npmDist
66
/denoDist
7-
/benchmarkDist
87
/npm
98
/deno
109

.eslintrc.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,15 @@ overrides:
670670
rules:
671671
node/no-missing-require: off
672672
no-console: off
673+
- files: 'benchmark/**'
674+
rules:
675+
node/no-sync: off
676+
node/no-missing-require: off
677+
import/no-nodejs-modules: off
678+
import/no-commonjs: off
679+
no-console: off
680+
no-await-in-loop: off
681+
no-restricted-syntax: off
673682
- files: 'resources/**'
674683
rules:
675684
node/no-unpublished-import: off

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,5 @@
1010
/coverage
1111
/npmDist
1212
/denoDist
13-
/benchmarkDist
1413
/npm
1514
/deno

.prettierignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
/coverage
55
/npmDist
66
/denoDist
7-
/benchmarkDist
87
/npm
98
/deno
109

resources/benchmark.js renamed to benchmark/benchmark.js

Lines changed: 107 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@ const path = require('path');
66
const assert = require('assert');
77
const cp = require('child_process');
88

9-
const { red, green, yellow, cyan, grey } = require('./colors');
10-
const { exec, rmdirRecursive, readdirRecursive } = require('./utils');
11-
129
const NS_PER_SEC = 1e9;
1310
const LOCAL = 'local';
1411

@@ -21,64 +18,76 @@ function localDir(...paths) {
2118
return path.join(__dirname, '..', ...paths);
2219
}
2320

21+
function exec(command, options = {}) {
22+
const result = cp.execSync(command, {
23+
encoding: 'utf-8',
24+
stdio: ['inherit', 'pipe', 'inherit'],
25+
...options,
26+
});
27+
return result && result.trimEnd();
28+
}
29+
2430
// Build a benchmark-friendly environment for the given revision
2531
// and returns path to its 'dist' directory.
26-
function prepareRevision(revision) {
27-
console.log(`🍳 Preparing ${revision}...`);
32+
function prepareBenchmarkProjects(revisionList) {
33+
const tmpDir = path.join(os.tmpdir(), 'graphql-js-benchmark');
34+
fs.mkdirSync(tmpDir, { recursive: true });
35+
36+
const setupDir = path.join(tmpDir, 'setup');
37+
fs.rmdirSync(setupDir, { recursive: true });
38+
fs.mkdirSync(setupDir);
39+
40+
return revisionList.map((revision) => {
41+
console.log(`🍳 Preparing ${revision}...`);
42+
const projectPath = path.join(setupDir, revision);
43+
fs.rmdirSync(projectPath, { recursive: true });
44+
fs.mkdirSync(projectPath);
45+
46+
fs.writeFileSync(
47+
path.join(projectPath, 'package.json'),
48+
'{ "private": true }',
49+
);
50+
exec('npm --quiet install ' + prepareNPMPackage(revision), {
51+
cwd: projectPath,
52+
});
53+
exec(`cp -R ${localDir('benchmark')} ${projectPath}`);
2854

29-
if (revision === LOCAL) {
30-
return babelBuild(localDir());
31-
}
55+
return { revision, projectPath };
56+
});
3257

33-
// Returns the complete git hash for a given git revision reference.
34-
const hash = exec(`git rev-parse "${revision}"`);
58+
function prepareNPMPackage(revision) {
59+
if (revision === LOCAL) {
60+
const repoDir = localDir();
61+
const archivePath = path.join(tmpDir, 'graphql-local.tgz');
62+
fs.renameSync(buildNPMArchive(repoDir), archivePath);
63+
return archivePath;
64+
}
3565

36-
const dir = path.join(os.tmpdir(), 'graphql-js-benchmark', hash);
37-
rmdirRecursive(dir);
38-
fs.mkdirSync(dir, { recursive: true });
66+
// Returns the complete git hash for a given git revision reference.
67+
const hash = exec(`git rev-parse "${revision}"`);
3968

40-
exec(`git archive "${hash}" | tar -xC "${dir}"`);
41-
exec('npm ci', { cwd: dir });
69+
const archivePath = path.join(tmpDir, `graphql-${hash}.tgz`);
70+
if (fs.existsSync(archivePath)) {
71+
return archivePath;
72+
}
4273

43-
for (const file of findFiles(localDir('src'), '*/__tests__/*')) {
44-
const from = localDir('src', file);
45-
const to = path.join(dir, 'src', file);
46-
fs.copyFileSync(from, to);
74+
const repoDir = path.join(tmpDir, hash);
75+
fs.rmdirSync(repoDir, { recursive: true });
76+
fs.mkdirSync(repoDir);
77+
exec(`git archive "${hash}" | tar -xC "${repoDir}"`);
78+
exec('npm --quiet ci', { cwd: repoDir });
79+
fs.renameSync(buildNPMArchive(repoDir), archivePath);
80+
fs.rmdirSync(repoDir, { recursive: true });
81+
return archivePath;
4782
}
48-
exec(`cp -R "${localDir()}/src/__fixtures__/" "${dir}/src/__fixtures__/"`);
4983

50-
return babelBuild(dir);
51-
}
84+
function buildNPMArchive(repoDir) {
85+
exec('npm --quiet run build:npm', { cwd: repoDir });
5286

53-
function babelBuild(dir) {
54-
const oldCWD = process.cwd();
55-
process.chdir(dir);
56-
57-
rmdirRecursive('./benchmarkDist');
58-
fs.mkdirSync('./benchmarkDist');
59-
60-
const babelPath = path.join(dir, 'node_modules', '@babel', 'core');
61-
const babel = require(babelPath);
62-
for (const filepath of readdirRecursive('./src')) {
63-
const srcPath = path.join('./src', filepath);
64-
const destPath = path.join('./benchmarkDist', filepath);
65-
66-
fs.mkdirSync(path.dirname(destPath), { recursive: true });
67-
if (filepath.endsWith('.js')) {
68-
const cjs = babel.transformFileSync(srcPath, { envName: 'cjs' }).code;
69-
fs.writeFileSync(destPath, cjs);
70-
} else {
71-
fs.copyFileSync(srcPath, destPath);
72-
}
87+
const distDir = path.join(repoDir, 'npmDist');
88+
const archiveName = exec(`npm --quiet pack ${distDir}`, { cwd: repoDir });
89+
return path.join(repoDir, archiveName);
7390
}
74-
75-
process.chdir(oldCWD);
76-
return path.join(dir, 'benchmarkDist');
77-
}
78-
79-
function findFiles(cwd, pattern) {
80-
const out = exec(`find . -path '${pattern}'`, { cwd });
81-
return out.split('\n').filter(Boolean);
8291
}
8392

8493
async function collectSamples(modulePath) {
@@ -220,17 +229,14 @@ function maxBy(array, fn) {
220229
}
221230

222231
// Prepare all revisions and run benchmarks matching a pattern against them.
223-
async function prepareAndRunBenchmarks(benchmarkPatterns, revisions) {
224-
const environments = revisions.map((revision) => ({
225-
revision,
226-
distPath: prepareRevision(revision),
227-
}));
232+
async function runBenchmarks(benchmarks, revisions) {
233+
const benchmarkProjects = prepareBenchmarkProjects(revisions);
228234

229-
for (const benchmark of matchBenchmarks(benchmarkPatterns)) {
235+
for (const benchmark of benchmarks) {
230236
const results = [];
231-
for (let i = 0; i < environments.length; ++i) {
232-
const environment = environments[i];
233-
const modulePath = path.join(environment.distPath, benchmark);
237+
for (let i = 0; i < benchmarkProjects.length; ++i) {
238+
const { revision, projectPath } = benchmarkProjects[i];
239+
const modulePath = path.join(projectPath, benchmark);
234240

235241
if (i === 0) {
236242
const { name } = await sampleModule(modulePath);
@@ -241,13 +247,13 @@ async function prepareAndRunBenchmarks(benchmarkPatterns, revisions) {
241247
const samples = await collectSamples(modulePath);
242248

243249
results.push({
244-
name: environment.revision,
250+
name: revision,
245251
samples,
246252
...computeStats(samples),
247253
});
248254
process.stdout.write(' ' + cyan(i + 1) + ' tests completed.\u000D');
249255
} catch (error) {
250-
console.log(' ' + environment.revision + ': ' + red(String(error)));
256+
console.log(' ' + revision + ': ' + red(String(error)));
251257
}
252258
}
253259
console.log('\n');
@@ -257,57 +263,74 @@ async function prepareAndRunBenchmarks(benchmarkPatterns, revisions) {
257263
}
258264
}
259265

260-
// Find all benchmark tests to be run.
261-
function matchBenchmarks(patterns) {
262-
let benchmarks = findFiles(localDir('src'), '*/__tests__/*-benchmark.js');
263-
if (patterns.length > 0) {
264-
benchmarks = benchmarks.filter((benchmark) =>
265-
patterns.some((pattern) => path.join('src', benchmark).includes(pattern)),
266-
);
267-
}
268-
269-
if (benchmarks.length === 0) {
270-
console.warn('No benchmarks matching: ' + patterns.map(bold).join(''));
271-
}
272-
273-
return benchmarks;
274-
}
275-
276266
function getArguments(argv) {
277267
const revsIdx = argv.indexOf('--revs');
278268
const revsArgs = revsIdx === -1 ? [] : argv.slice(revsIdx + 1);
279-
const benchmarkPatterns = revsIdx === -1 ? argv : argv.slice(0, revsIdx);
269+
const specificBenchmarks = revsIdx === -1 ? argv : argv.slice(0, revsIdx);
280270
let assumeArgs;
281271
let revisions;
282272
switch (revsArgs.length) {
283273
case 0:
284-
assumeArgs = [...benchmarkPatterns, '--revs', 'local', 'HEAD'];
274+
assumeArgs = [...specificBenchmarks, '--revs', 'local', 'HEAD'];
285275
revisions = [LOCAL, 'HEAD'];
286276
break;
287277
case 1:
288-
assumeArgs = [...benchmarkPatterns, '--revs', 'local', revsArgs[0]];
278+
assumeArgs = [...specificBenchmarks, '--revs', 'local', revsArgs[0]];
289279
revisions = [LOCAL, revsArgs[0]];
290280
break;
291281
default:
292282
revisions = revsArgs;
293283
break;
294284
}
285+
295286
if (assumeArgs) {
296287
console.warn(
297288
'Assuming you meant: ' + bold('benchmark ' + assumeArgs.join(' ')),
298289
);
299290
}
300-
return { benchmarkPatterns, revisions };
291+
292+
return { specificBenchmarks, revisions };
301293
}
302294

303295
function bold(str) {
304296
return '\u001b[1m' + str + '\u001b[0m';
305297
}
306298

299+
function red(str) {
300+
return '\u001b[31m' + str + '\u001b[0m';
301+
}
302+
303+
function green(str) {
304+
return '\u001b[32m' + str + '\u001b[0m';
305+
}
306+
307+
function yellow(str) {
308+
return '\u001b[33m' + str + '\u001b[0m';
309+
}
310+
311+
function cyan(str) {
312+
return '\u001b[36m' + str + '\u001b[0m';
313+
}
314+
315+
function grey(str) {
316+
return '\u001b[90m' + str + '\u001b[0m';
317+
}
318+
319+
function findAllBenchmarks() {
320+
return fs
321+
.readdirSync(localDir('benchmark'), { withFileTypes: true })
322+
.filter((dirent) => dirent.isFile())
323+
.map((dirent) => dirent.name)
324+
.filter((name) => name.endsWith('-benchmark.js'))
325+
.map((name) => path.join('benchmark', name));
326+
}
327+
307328
// Get the revisions and make things happen!
308329
if (require.main === module) {
309-
const { benchmarkPatterns, revisions } = getArguments(process.argv.slice(2));
310-
prepareAndRunBenchmarks(benchmarkPatterns, revisions).catch((error) => {
330+
const { specificBenchmarks, revisions } = getArguments(process.argv.slice(2));
331+
const benchmarks =
332+
specificBenchmarks.length > 0 ? specificBenchmarks : findAllBenchmarks();
333+
runBenchmarks(benchmarks, revisions).catch((error) => {
311334
console.error(error);
312335
process.exit(1);
313336
});
@@ -350,7 +373,7 @@ function sampleModule(modulePath) {
350373
'--noconcurrent_sweeping',
351374
'--predictable',
352375
'--expose-gc',
353-
'-e',
376+
'--eval',
354377
sampleCode,
355378
],
356379
{

benchmark/buildASTSchema-benchmark.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
'use strict';
2+
3+
const { parse } = require('graphql/language/parser.js');
4+
const { buildASTSchema } = require('graphql/utilities/buildASTSchema.js');
5+
6+
const { bigSchemaSDL } = require('./fixtures.js');
7+
8+
const schemaAST = parse(bigSchemaSDL);
9+
10+
module.exports = {
11+
name: 'Build Schema from AST',
12+
count: 10,
13+
measure() {
14+
buildASTSchema(schemaAST, { assumeValid: true });
15+
},
16+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use strict';
2+
3+
const { buildClientSchema } = require('graphql/utilities/buildClientSchema.js');
4+
5+
const { bigSchemaIntrospectionResult } = require('./fixtures.js');
6+
7+
module.exports = {
8+
name: 'Build Schema from Introspection',
9+
count: 10,
10+
measure() {
11+
buildClientSchema(bigSchemaIntrospectionResult.data, { assumeValid: true });
12+
},
13+
};

benchmark/fixtures.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use strict';
2+
3+
const fs = require('fs');
4+
const path = require('path');
5+
6+
exports.bigSchemaSDL = fs.readFileSync(
7+
path.join(__dirname, 'github-schema.graphql'),
8+
'utf8',
9+
);
10+
11+
exports.bigSchemaIntrospectionResult = JSON.parse(
12+
fs.readFileSync(path.join(__dirname, 'github-schema.json'), 'utf8'),
13+
);
File renamed without changes.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict';
2+
3+
const { parse } = require('graphql/language/parser.js');
4+
const { executeSync } = require('graphql/execution/execute.js');
5+
const { buildSchema } = require('graphql/utilities/buildASTSchema.js');
6+
const {
7+
getIntrospectionQuery,
8+
} = require('graphql/utilities/getIntrospectionQuery.js');
9+
10+
const { bigSchemaSDL } = require('./fixtures.js');
11+
12+
const schema = buildSchema(bigSchemaSDL, { assumeValid: true });
13+
const document = parse(getIntrospectionQuery());
14+
15+
module.exports = {
16+
name: 'Execute Introspection Query',
17+
count: 10,
18+
measure() {
19+
executeSync({ schema, document });
20+
},
21+
};

0 commit comments

Comments
 (0)