Skip to content

Commit 19fbe55

Browse files
BridgeARcodebytere
authored andcommittedMar 30, 2020
benchmark: refactor helper into a class
This is the basis to refactor the helper to use modern class language features such as private fields. It also refactors the exports to use module.exports. That way it's immediately clear what parts are exported. PR-URL: #31396 Reviewed-By: Sam Roberts <vieuxtech@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
1 parent a305ae2 commit 19fbe55

File tree

1 file changed

+215
-214
lines changed

1 file changed

+215
-214
lines changed
 

‎benchmark/common.js

+215-214
Original file line numberDiff line numberDiff line change
@@ -3,222 +3,225 @@
33
const child_process = require('child_process');
44
const http_benchmarkers = require('./_http-benchmarkers.js');
55

6-
exports.buildType = process.features.debug ? 'Debug' : 'Release';
7-
8-
exports.createBenchmark = function(fn, configs, options) {
9-
return new Benchmark(fn, configs, options);
10-
};
11-
12-
function Benchmark(fn, configs, options) {
13-
// Use the file name as the name of the benchmark
14-
this.name = require.main.filename.slice(__dirname.length + 1);
15-
// Parse job-specific configuration from the command line arguments
16-
const parsed_args = this._parseArgs(process.argv.slice(2), configs);
17-
this.options = parsed_args.cli;
18-
this.extra_options = parsed_args.extra;
19-
// The configuration list as a queue of jobs
20-
this.queue = this._queue(this.options);
21-
// The configuration of the current job, head of the queue
22-
this.config = this.queue[0];
23-
// Execution arguments i.e. flags used to run the jobs
24-
this.flags = [];
25-
if (options && options.flags) {
26-
this.flags = this.flags.concat(options.flags);
27-
}
28-
if (process.env.NODE_BENCHMARK_FLAGS) {
29-
const flags = process.env.NODE_BENCHMARK_FLAGS.split(/\s+/);
30-
this.flags = this.flags.concat(flags);
31-
}
32-
// Holds process.hrtime value
33-
this._time = [0, 0];
34-
// Used to make sure a benchmark only start a timer once
35-
this._started = false;
36-
this._ended = false;
37-
38-
// this._run will use fork() to create a new process for each configuration
39-
// combination.
40-
if (process.env.hasOwnProperty('NODE_RUN_BENCHMARK_FN')) {
41-
process.nextTick(() => fn(this.config));
42-
} else {
43-
process.nextTick(() => this._run());
44-
}
45-
}
46-
47-
Benchmark.prototype._parseArgs = function(argv, configs) {
48-
const cliOptions = {};
49-
const extraOptions = {};
50-
const validArgRE = /^(.+?)=([\s\S]*)$/;
51-
// Parse configuration arguments
52-
for (const arg of argv) {
53-
const match = arg.match(validArgRE);
54-
if (!match) {
55-
console.error(`bad argument: ${arg}`);
56-
process.exit(1);
6+
class Benchmark {
7+
constructor(fn, configs, options) {
8+
// Use the file name as the name of the benchmark
9+
this.name = require.main.filename.slice(__dirname.length + 1);
10+
// Parse job-specific configuration from the command line arguments
11+
const parsed_args = this._parseArgs(process.argv.slice(2), configs);
12+
this.options = parsed_args.cli;
13+
this.extra_options = parsed_args.extra;
14+
// The configuration list as a queue of jobs
15+
this.queue = this._queue(this.options);
16+
// The configuration of the current job, head of the queue
17+
this.config = this.queue[0];
18+
// Execution arguments i.e. flags used to run the jobs
19+
this.flags = [];
20+
if (options && options.flags) {
21+
this.flags = this.flags.concat(options.flags);
5722
}
58-
const config = match[1];
59-
60-
if (configs[config]) {
61-
// Infer the type from the config object and parse accordingly
62-
const isNumber = typeof configs[config][0] === 'number';
63-
const value = isNumber ? +match[2] : match[2];
64-
if (!cliOptions[config])
65-
cliOptions[config] = [];
66-
cliOptions[config].push(value);
23+
if (process.env.NODE_BENCHMARK_FLAGS) {
24+
const flags = process.env.NODE_BENCHMARK_FLAGS.split(/\s+/);
25+
this.flags = this.flags.concat(flags);
26+
}
27+
// Holds process.hrtime value
28+
this._time = [0, 0];
29+
// Used to make sure a benchmark only start a timer once
30+
this._started = false;
31+
this._ended = false;
32+
33+
// this._run will use fork() to create a new process for each configuration
34+
// combination.
35+
if (process.env.hasOwnProperty('NODE_RUN_BENCHMARK_FN')) {
36+
process.nextTick(() => fn(this.config));
6737
} else {
68-
extraOptions[config] = match[2];
38+
process.nextTick(() => this._run());
6939
}
7040
}
71-
return { cli: Object.assign({}, configs, cliOptions), extra: extraOptions };
72-
};
73-
74-
Benchmark.prototype._queue = function(options) {
75-
const queue = [];
76-
const keys = Object.keys(options);
77-
78-
// Perform a depth-first walk though all options to generate a
79-
// configuration list that contains all combinations.
80-
function recursive(keyIndex, prevConfig) {
81-
const key = keys[keyIndex];
82-
const values = options[key];
83-
const type = typeof values[0];
8441

85-
for (const value of values) {
86-
if (typeof value !== 'number' && typeof value !== 'string') {
87-
throw new TypeError(`configuration "${key}" had type ${typeof value}`);
42+
_parseArgs(argv, configs) {
43+
const cliOptions = {};
44+
const extraOptions = {};
45+
const validArgRE = /^(.+?)=([\s\S]*)$/;
46+
// Parse configuration arguments
47+
for (const arg of argv) {
48+
const match = arg.match(validArgRE);
49+
if (!match) {
50+
console.error(`bad argument: ${arg}`);
51+
process.exit(1);
8852
}
89-
if (typeof value !== type) {
90-
// This is a requirement for being able to consistently and predictably
91-
// parse CLI provided configuration values.
92-
throw new TypeError(`configuration "${key}" has mixed types`);
53+
const config = match[1];
54+
55+
if (configs[config]) {
56+
// Infer the type from the config object and parse accordingly
57+
const isNumber = typeof configs[config][0] === 'number';
58+
const value = isNumber ? +match[2] : match[2];
59+
if (!cliOptions[config])
60+
cliOptions[config] = [];
61+
cliOptions[config].push(value);
62+
} else {
63+
extraOptions[config] = match[2];
9364
}
65+
}
66+
return { cli: Object.assign({}, configs, cliOptions), extra: extraOptions };
67+
}
9468

95-
const currConfig = Object.assign({ [key]: value }, prevConfig);
96-
97-
if (keyIndex + 1 < keys.length) {
98-
recursive(keyIndex + 1, currConfig);
99-
} else {
100-
queue.push(currConfig);
69+
_queue(options) {
70+
const queue = [];
71+
const keys = Object.keys(options);
72+
73+
// Perform a depth-first walk though all options to generate a
74+
// configuration list that contains all combinations.
75+
function recursive(keyIndex, prevConfig) {
76+
const key = keys[keyIndex];
77+
const values = options[key];
78+
const type = typeof values[0];
79+
80+
for (const value of values) {
81+
if (typeof value !== 'number' && typeof value !== 'string') {
82+
throw new TypeError(
83+
`configuration "${key}" had type ${typeof value}`);
84+
}
85+
if (typeof value !== type) {
86+
// This is a requirement for being able to consistently and
87+
// predictably parse CLI provided configuration values.
88+
throw new TypeError(`configuration "${key}" has mixed types`);
89+
}
90+
91+
const currConfig = Object.assign({ [key]: value }, prevConfig);
92+
93+
if (keyIndex + 1 < keys.length) {
94+
recursive(keyIndex + 1, currConfig);
95+
} else {
96+
queue.push(currConfig);
97+
}
10198
}
10299
}
100+
101+
if (keys.length > 0) {
102+
recursive(0, {});
103+
} else {
104+
queue.push({});
105+
}
106+
107+
return queue;
103108
}
104109

105-
if (keys.length > 0) {
106-
recursive(0, {});
107-
} else {
108-
queue.push({});
110+
http(options, cb) {
111+
const self = this;
112+
const http_options = Object.assign({ }, options);
113+
http_options.benchmarker = http_options.benchmarker ||
114+
self.config.benchmarker ||
115+
self.extra_options.benchmarker ||
116+
exports.default_http_benchmarker;
117+
http_benchmarkers.run(
118+
http_options, (error, code, used_benchmarker, result, elapsed) => {
119+
if (cb) {
120+
cb(code);
121+
}
122+
if (error) {
123+
console.error(error);
124+
process.exit(code || 1);
125+
}
126+
self.config.benchmarker = used_benchmarker;
127+
self.report(result, elapsed);
128+
}
129+
);
109130
}
110131

111-
return queue;
112-
};
132+
_run() {
133+
const self = this;
134+
// If forked, report to the parent.
135+
if (process.send) {
136+
process.send({
137+
type: 'config',
138+
name: this.name,
139+
queueLength: this.queue.length,
140+
});
141+
}
142+
143+
(function recursive(queueIndex) {
144+
const config = self.queue[queueIndex];
113145

114-
// Benchmark an http server.
115-
exports.default_http_benchmarker =
116-
http_benchmarkers.default_http_benchmarker;
117-
exports.PORT = http_benchmarkers.PORT;
118-
119-
Benchmark.prototype.http = function(options, cb) {
120-
const self = this;
121-
const http_options = Object.assign({ }, options);
122-
http_options.benchmarker = http_options.benchmarker ||
123-
self.config.benchmarker ||
124-
self.extra_options.benchmarker ||
125-
exports.default_http_benchmarker;
126-
http_benchmarkers.run(
127-
http_options, (error, code, used_benchmarker, result, elapsed) => {
128-
if (cb) {
129-
cb(code);
146+
// Set NODE_RUN_BENCHMARK_FN to indicate that the child shouldn't
147+
// construct a configuration queue, but just execute the benchmark
148+
// function.
149+
const childEnv = Object.assign({}, process.env);
150+
childEnv.NODE_RUN_BENCHMARK_FN = '';
151+
152+
// Create configuration arguments
153+
const childArgs = [];
154+
for (const key of Object.keys(config)) {
155+
childArgs.push(`${key}=${config[key]}`);
130156
}
131-
if (error) {
132-
console.error(error);
133-
process.exit(code || 1);
157+
for (const key of Object.keys(self.extra_options)) {
158+
childArgs.push(`${key}=${self.extra_options[key]}`);
134159
}
135-
self.config.benchmarker = used_benchmarker;
136-
self.report(result, elapsed);
137-
}
138-
);
139-
};
140160

141-
Benchmark.prototype._run = function() {
142-
const self = this;
143-
// If forked, report to the parent.
144-
if (process.send) {
145-
process.send({
146-
type: 'config',
147-
name: this.name,
148-
queueLength: this.queue.length,
149-
});
161+
const child = child_process.fork(require.main.filename, childArgs, {
162+
env: childEnv,
163+
execArgv: self.flags.concat(process.execArgv),
164+
});
165+
child.on('message', sendResult);
166+
child.on('close', (code) => {
167+
if (code) {
168+
process.exit(code);
169+
}
170+
171+
if (queueIndex + 1 < self.queue.length) {
172+
recursive(queueIndex + 1);
173+
}
174+
});
175+
})(0);
150176
}
151177

152-
(function recursive(queueIndex) {
153-
const config = self.queue[queueIndex];
178+
start() {
179+
if (this._started) {
180+
throw new Error('Called start more than once in a single benchmark');
181+
}
182+
this._started = true;
183+
this._time = process.hrtime();
184+
}
154185

155-
// Set NODE_RUN_BENCHMARK_FN to indicate that the child shouldn't construct
156-
// a configuration queue, but just execute the benchmark function.
157-
const childEnv = Object.assign({}, process.env);
158-
childEnv.NODE_RUN_BENCHMARK_FN = '';
186+
end(operations) {
187+
// Get elapsed time now and do error checking later for accuracy.
188+
const elapsed = process.hrtime(this._time);
159189

160-
// Create configuration arguments
161-
const childArgs = [];
162-
for (const key of Object.keys(config)) {
163-
childArgs.push(`${key}=${config[key]}`);
190+
if (!this._started) {
191+
throw new Error('called end without start');
164192
}
165-
for (const key of Object.keys(self.extra_options)) {
166-
childArgs.push(`${key}=${self.extra_options[key]}`);
193+
if (this._ended) {
194+
throw new Error('called end multiple times');
195+
}
196+
if (typeof operations !== 'number') {
197+
throw new Error('called end() without specifying operation count');
198+
}
199+
if (!process.env.NODEJS_BENCHMARK_ZERO_ALLOWED && operations <= 0) {
200+
throw new Error('called end() with operation count <= 0');
201+
}
202+
if (elapsed[0] === 0 && elapsed[1] === 0) {
203+
if (!process.env.NODEJS_BENCHMARK_ZERO_ALLOWED)
204+
throw new Error('insufficient clock precision for short benchmark');
205+
// Avoid dividing by zero
206+
elapsed[1] = 1;
167207
}
168208

169-
const child = child_process.fork(require.main.filename, childArgs, {
170-
env: childEnv,
171-
execArgv: self.flags.concat(process.execArgv),
172-
});
173-
child.on('message', sendResult);
174-
child.on('close', (code) => {
175-
if (code) {
176-
process.exit(code);
177-
}
178-
179-
if (queueIndex + 1 < self.queue.length) {
180-
recursive(queueIndex + 1);
181-
}
182-
});
183-
})(0);
184-
};
185-
186-
Benchmark.prototype.start = function() {
187-
if (this._started) {
188-
throw new Error('Called start more than once in a single benchmark');
209+
this._ended = true;
210+
const time = elapsed[0] + elapsed[1] / 1e9;
211+
const rate = operations / time;
212+
this.report(rate, elapsed);
189213
}
190-
this._started = true;
191-
this._time = process.hrtime();
192-
};
193214

194-
Benchmark.prototype.end = function(operations) {
195-
// Get elapsed time now and do error checking later for accuracy.
196-
const elapsed = process.hrtime(this._time);
197-
198-
if (!this._started) {
199-
throw new Error('called end without start');
200-
}
201-
if (this._ended) {
202-
throw new Error('called end multiple times');
203-
}
204-
if (typeof operations !== 'number') {
205-
throw new Error('called end() without specifying operation count');
206-
}
207-
if (!process.env.NODEJS_BENCHMARK_ZERO_ALLOWED && operations <= 0) {
208-
throw new Error('called end() with operation count <= 0');
209-
}
210-
if (elapsed[0] === 0 && elapsed[1] === 0) {
211-
if (!process.env.NODEJS_BENCHMARK_ZERO_ALLOWED)
212-
throw new Error('insufficient clock precision for short benchmark');
213-
// Avoid dividing by zero
214-
elapsed[1] = 1;
215+
report(rate, elapsed) {
216+
sendResult({
217+
name: this.name,
218+
conf: this.config,
219+
rate: rate,
220+
time: elapsed[0] + elapsed[1] / 1e9,
221+
type: 'report',
222+
});
215223
}
216-
217-
this._ended = true;
218-
const time = elapsed[0] + elapsed[1] / 1e9;
219-
const rate = operations / time;
220-
this.report(rate, elapsed);
221-
};
224+
}
222225

223226
function formatResult(data) {
224227
// Construct configuration string, " A=a, B=b, ..."
@@ -242,27 +245,6 @@ function sendResult(data) {
242245
console.log(formatResult(data));
243246
}
244247
}
245-
exports.sendResult = sendResult;
246-
247-
Benchmark.prototype.report = function(rate, elapsed) {
248-
sendResult({
249-
name: this.name,
250-
conf: this.config,
251-
rate: rate,
252-
time: elapsed[0] + elapsed[1] / 1e9,
253-
type: 'report',
254-
});
255-
};
256-
257-
exports.binding = function(bindingName) {
258-
try {
259-
const { internalBinding } = require('internal/test/binding');
260-
261-
return internalBinding(bindingName);
262-
} catch {
263-
return process.binding(bindingName);
264-
}
265-
};
266248

267249
const urls = {
268250
long: 'http://nodejs.org:89/docs/latest/api/foo/bar/qua/13949281/0f28b/' +
@@ -278,7 +260,6 @@ const urls = {
278260
percent: 'https://%E4%BD%A0/foo',
279261
dot: 'https://example.org/./a/../b/./c',
280262
};
281-
exports.urls = urls;
282263

283264
const searchParams = {
284265
noencode: 'foo=bar&baz=quux&xyzzy=thud',
@@ -293,7 +274,6 @@ const searchParams = {
293274
manyblankpairs: '&&&&&&&&&&&&&&&&&&&&&&&&',
294275
altspaces: 'foo+bar=baz+quux&xyzzy+thud=quuy+quuz&abc=def+ghi',
295276
};
296-
exports.searchParams = searchParams;
297277

298278
function getUrlData(withBase) {
299279
const data = require('../test/fixtures/wpt/url/resources/urltestdata.json');
@@ -309,8 +289,6 @@ function getUrlData(withBase) {
309289
return result;
310290
}
311291

312-
exports.urlDataTypes = Object.keys(urls).concat(['wpt']);
313-
314292
/**
315293
* Generate an array of data for URL benchmarks to use.
316294
* The size of the resulting data set is the original data size * 2 ** `e`.
@@ -354,4 +332,27 @@ function bakeUrlData(type, e = 0, withBase = false, asUrl = false) {
354332
}
355333
return result;
356334
}
357-
exports.bakeUrlData = bakeUrlData;
335+
336+
module.exports = {
337+
PORT: http_benchmarkers.PORT,
338+
bakeUrlData,
339+
binding(bindingName) {
340+
try {
341+
const { internalBinding } = require('internal/test/binding');
342+
343+
return internalBinding(bindingName);
344+
} catch {
345+
return process.binding(bindingName);
346+
}
347+
},
348+
buildType: process.features.debug ? 'Debug' : 'Release',
349+
createBenchmark(fn, configs, options) {
350+
return new Benchmark(fn, configs, options);
351+
},
352+
// Benchmark an http server.
353+
default_http_benchmarker: http_benchmarkers.default_http_benchmarker,
354+
sendResult,
355+
searchParams,
356+
urlDataTypes: Object.keys(urls).concat(['wpt']),
357+
urls,
358+
};

0 commit comments

Comments
 (0)
Please sign in to comment.