overtake 0.0.1-rc3 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/cli.js +11 -7
  2. package/index.d.ts +24 -50
  3. package/index.js +103 -173
  4. package/package.json +2 -2
  5. package/reporter.js +0 -90
package/cli.js CHANGED
@@ -1,19 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { promisify } from 'util';
4
+ import Path from 'path';
4
5
  import glob from 'glob';
5
- import { Overtake } from './index.js';
6
- import { defaultReporter } from './reporter.js';
6
+ import { load, benchmark, setup, teardown, measure, perform, run, defaultReporter } from './index.js';
7
7
 
8
8
  const globAsync = promisify(glob);
9
9
  const pattern = process.argv[2] || '**/__benchmarks__/**/*.js';
10
- const overtake = new Overtake({});
10
+
11
+ Object.assign(globalThis, { benchmark, setup, teardown, measure, perform });
11
12
 
12
13
  (async () => {
13
14
  const files = await globAsync(pattern);
14
- await overtake.load(files);
15
- if (overtake.reporters.length === 0) {
16
- reporter(defaultReporter);
15
+ const scripts = [];
16
+ for (const file of files) {
17
+ const filename = Path.resolve(file);
18
+ const script = await load(filename);
19
+ scripts.push(script);
17
20
  }
18
- await overtake.run();
21
+
22
+ await run(scripts, defaultReporter);
19
23
  })().catch((e) => console.error(e));
package/index.d.ts CHANGED
@@ -1,5 +1,3 @@
1
- import { Event } from 'evnty';
2
-
3
1
  declare global {
4
2
  type CanBePromise<T> = Promise<T> | T;
5
3
 
@@ -11,8 +9,15 @@ declare global {
11
9
  max: number;
12
10
  sum: number;
13
11
  avg: number;
14
- med: number;
15
12
  mode: number;
13
+ p1: number;
14
+ p5: number;
15
+ p20: number;
16
+ p33: number;
17
+ p50: number;
18
+ med: number;
19
+ p66: number;
20
+ p80: number;
16
21
  p90: number;
17
22
  p95: number;
18
23
  p99: number;
@@ -23,52 +28,6 @@ declare global {
23
28
  total: number;
24
29
  }
25
30
 
26
- interface Overtake {
27
- onLoad: Event<any>;
28
-
29
- onRun: Event<any>;
30
-
31
- onComplete: Event<any>;
32
-
33
- onScriptRegister: Event<any>;
34
-
35
- onScriptStart: Event<any>;
36
-
37
- onScriptComplete: Event<any>;
38
-
39
- onSuiteRegister: Event<any>;
40
-
41
- onSuiteStart: Event<any>;
42
-
43
- onSuiteComplete: Event<any>;
44
-
45
- onSetupRegister: Event<any>;
46
-
47
- onTeardownRegister: Event<any>;
48
-
49
- onMeasureRegister: Event<any>;
50
-
51
- onMeasureStart: Event<any>;
52
-
53
- onMeasureComplete: Event<any>;
54
-
55
- onPerformRegister: Event<any>;
56
-
57
- onPerformStart: Event<any>;
58
-
59
- onPerformProgress: Event<any>;
60
-
61
- onPerformComplete: Event<any>;
62
-
63
- onReport: Event<Report>;
64
- }
65
-
66
- function benchmark(title: string, init: () => void): void;
67
-
68
- function setup<C>(init: () => CanBePromise<C>): any;
69
-
70
- function teardown<C>(teardown: (context: C) => CanBePromise<void>): void;
71
-
72
31
  type MeasureInitResult = CanBePromise<() => void>;
73
32
 
74
33
  function measure(title: string, init: () => MeasureInitResult): void;
@@ -78,5 +37,20 @@ declare global {
78
37
 
79
38
  function perform<A>(title: string, counter: number, args: A): void;
80
39
 
81
- function reporter(reporter: (overtake: Overtake) => void): void;
40
+ function setup<C>(init: () => CanBePromise<C>): void;
41
+
42
+ function teardown<C>(teardown: (context: C) => CanBePromise<void>): void;
43
+
44
+ interface Suite {
45
+ title: string;
46
+ setup: typeof setup;
47
+ teardown: typeof teardown;
48
+ measures: typeof measure[];
49
+ performs: typeof perform[];
50
+ init: () => void;
51
+ }
52
+
53
+ function benchmark(title: string, init: () => void): void;
54
+
55
+ function script(filename): Promise<Suite[]>;
82
56
  }
package/index.js CHANGED
@@ -1,183 +1,112 @@
1
- import Path from 'path';
1
+ import { createContext } from 'conode';
2
2
  import WorkerThreads from 'worker_threads';
3
- import { Event } from 'evnty';
4
3
 
5
- export const NOOP = () => {};
6
-
7
- export class Perform {
8
- title = '';
9
-
10
- count = 0;
11
-
12
- args;
13
-
14
- constructor(overtake, title, count, args) {
15
- this.title = title;
16
- this.count = count;
17
- this.args = args;
18
- }
19
- }
20
-
21
- export class Measure {
22
- title = '';
4
+ const overtakeContext = createContext();
5
+ const suiteContext = createContext();
23
6
 
24
- init = NOOP;
25
-
26
- constructor(overtake, title, init = NOOP) {
27
- this.title = title;
28
- this.init = init;
29
- }
30
- }
31
-
32
- export class Suite {
33
- title = '';
34
-
35
- setup = NOOP;
36
-
37
- measures = [];
38
-
39
- performs = [];
7
+ export const NOOP = () => {};
40
8
 
41
- teardown = NOOP;
9
+ export const setup = (fn) => {
10
+ suiteContext.getContext().setup = fn;
11
+ };
42
12
 
43
- #init = NOOP;
13
+ export const teardown = (fn) => {
14
+ suiteContext.getContext().teardown = fn;
15
+ };
44
16
 
45
- #overtake = null;
17
+ export const measure = (title, fn) => {
18
+ suiteContext.getContext().measures.push({ title, init: fn });
19
+ };
46
20
 
47
- constructor(overtake, title, init = NOOP) {
48
- this.#overtake = overtake;
49
- this.title = title;
50
- this.#init = init;
51
- }
21
+ export const perform = (title, count, args) => {
22
+ suiteContext.getContext().performs.push({ title, count, args });
23
+ };
52
24
 
53
- async init() {
54
- const unsubscribes = [
55
- this.#overtake.onSetupRegister.on((setup) => (this.setup = setup)),
56
- this.#overtake.onMeasureRegister.on((measure) => this.measures.push(measure)),
57
- this.#overtake.onPerformRegister.on((perform) => this.performs.push(perform)),
58
- this.#overtake.onTeardownRegister.on((teardown) => (this.teardown = teardown)),
59
- ];
60
- await this.#init();
61
- unsubscribes.forEach((unsubscribe) => unsubscribe());
62
- }
63
- }
25
+ export const benchmark = (title, fn) => {
26
+ const setup = NOOP;
27
+ const teardown = NOOP;
28
+ const measures = [];
29
+ const performs = [];
30
+
31
+ overtakeContext.getContext().suites.push({
32
+ title,
33
+ setup,
34
+ teardown,
35
+ measures,
36
+ performs,
37
+ init: fn,
38
+ });
39
+ };
64
40
 
65
- export class Script {
66
- onLoad = new Event();
41
+ export const load = async (filename) => {
42
+ const suites = [];
43
+ const script = { filename, suites };
44
+ await overtakeContext.contextualize(script, () => import(filename));
67
45
 
68
- filename = '';
46
+ return script;
47
+ };
48
+ const map = {
49
+ script: '⭐ Script ',
50
+ suite: '⇶ Suite ',
51
+ perform: '➤ Perform ',
52
+ measure: '✓ Measure',
53
+ };
69
54
 
70
- suites = [];
55
+ export const defaultReporter = async (type, title, test) => {
56
+ console.group(`${map[type]} ${title}`);
57
+ await test({ test: defaultReporter, output: (report) => console.table(report) });
58
+ console.groupEnd();
59
+ };
71
60
 
72
- constructor(filename) {
73
- this.filename = filename;
74
- }
61
+ const ACCURACY = 6;
62
+ export function formatFloat(value, digits = ACCURACY) {
63
+ return parseFloat(value.toFixed(digits));
75
64
  }
76
65
 
77
- export class Overtake {
78
- onLoad = new Event();
79
-
80
- onRun = new Event();
81
-
82
- onComplete = new Event();
83
-
84
- onScriptRegister = new Event();
85
-
86
- onScriptStart = new Event();
87
-
88
- onScriptComplete = new Event();
89
-
90
- onSuiteRegister = new Event();
91
-
92
- onSuiteStart = new Event();
93
-
94
- onSuiteComplete = new Event();
95
-
96
- onSetupRegister = new Event();
97
-
98
- onTeardownRegister = new Event();
99
-
100
- onMeasureRegister = new Event();
101
-
102
- onMeasureStart = new Event();
103
-
104
- onMeasureComplete = new Event();
105
-
106
- onPerformRegister = new Event();
107
-
108
- onPerformStart = new Event();
109
-
110
- onPerformProgress = new Event();
111
-
112
- onPerformComplete = new Event();
113
-
114
- onReport = new Event();
115
-
116
- scripts = [];
117
-
118
- reporters = [];
119
-
120
- constructor(options = {}) {
121
- Object.assign(globalThis, {
122
- benchmark: (title, init) => this.onSuiteRegister(new Suite(this, title, init)),
123
- setup: (init) => this.onSetupRegister(init),
124
- teardown: (init) => this.onTeardownRegister(init),
125
- measure: (title, init) => this.onMeasureRegister(new Measure(this, title, init)),
126
- perform: (title, count, args) => this.onPerformRegister(new Perform(this, title, count, args)),
127
- reporter: (reporter) => this.reporters.push(reporter(this)),
128
- });
129
- }
130
-
131
- async load(files) {
132
- this.onLoad(this);
133
- for (const file of files) {
134
- const filename = Path.resolve(file);
135
- const script = new Script(filename);
136
- const unsubscribe = this.onSuiteRegister.on((suite) => {
137
- script.suites.push(suite);
138
- });
139
- await import(filename);
140
- unsubscribe();
141
- this.scripts.push(script);
142
- this.onScriptRegister(script);
143
- }
144
- }
145
-
146
- async run() {
147
- this.onRun();
148
- for (const script of this.scripts) {
149
- this.onScriptStart(script);
66
+ export const run = async (scripts, reporter) => {
67
+ for (const script of scripts) {
68
+ await reporter('script', script.filename, async (scriptTest) => {
150
69
  for (const suite of script.suites) {
151
- await suite.init().catch((e) => console.error(e));
152
- this.onSuiteStart(suite);
153
- for (const measure of suite.measures) {
154
- this.onMeasureStart(measure);
70
+ await scriptTest.test('suite', suite.title, async (suiteTest) => {
71
+ await suiteContext.contextualize(suite, suite.init);
155
72
  for (const perform of suite.performs) {
156
- this.onPerformStart(perform);
157
- const result = await runWorker(
158
- {
159
- setup: suite.setup,
160
- teardown: suite.teardown,
161
- init: measure.init,
162
- count: perform.count,
163
- args: perform.args,
164
- },
165
- this.onPerformProgress
166
- );
167
- this.onPerformComplete(perform);
168
- this.onReport(result);
73
+ await suiteTest.test('perform', perform.title, async (performTest) => {
74
+ for (const measure of suite.measures) {
75
+ await performTest.test('measure', measure.title, async (measureTest) => {
76
+ const result = await runWorker({
77
+ setup: suite.setup,
78
+ teardown: suite.teardown,
79
+ init: measure.init,
80
+ count: perform.count,
81
+ args: perform.args,
82
+ });
83
+ if (result.success) {
84
+ measureTest.output({
85
+ [formatFloat(result.mode)]: {
86
+ total: formatFloat(result.total),
87
+ med: formatFloat(result.med),
88
+ p95: formatFloat(result.p95),
89
+ p99: formatFloat(result.p99),
90
+ },
91
+ });
92
+ } else {
93
+ measureTest.output({
94
+ error: {
95
+ reason: result.error,
96
+ },
97
+ });
98
+ }
99
+ });
100
+ }
101
+ });
169
102
  }
170
- this.onMeasureComplete(perform);
171
- }
172
- this.onSuiteComplete(perform);
103
+ });
173
104
  }
174
- this.onScriptComplete(perform);
175
- }
176
- this.onComplete(this);
105
+ });
177
106
  }
178
- }
107
+ };
179
108
 
180
- export async function runWorker({ args, count, ...options }, onProgress) {
109
+ export async function runWorker({ args, count, ...options }, onProgress = null) {
181
110
  const setupCode = options.setup.toString();
182
111
  const teardownCode = options.teardown.toString();
183
112
  const initCode = options.init.toString();
@@ -192,7 +121,7 @@ export async function runWorker({ args, count, ...options }, onProgress) {
192
121
  const worker = new WorkerThreads.Worker(new URL('runner.js', import.meta.url), { argv: [params] });
193
122
  return new Promise((resolve) => {
194
123
  worker.on('message', (data) => {
195
- if (data.type === 'progress') {
124
+ if (onProgress && data.type === 'progress') {
196
125
  onProgress(data);
197
126
  } else if (data.type === 'report') {
198
127
  resolve(data);
@@ -292,14 +221,7 @@ export async function start(input) {
292
221
  });
293
222
  buckets.sort((a, b) => a[1] - b[1]);
294
223
 
295
- const medIdx = Math.trunc((50 * timings.length) / 100);
296
- const med = timings[medIdx];
297
- const p90Idx = Math.trunc((90 * timings.length) / 100);
298
- const p90 = timings[p90Idx];
299
- const p95Idx = Math.trunc((95 * timings.length) / 100);
300
- const p95 = timings[p95Idx];
301
- const p99Idx = Math.trunc((99 * timings.length) / 100);
302
- const p99 = timings[p99Idx];
224
+ const percentile = (p) => timings[Math.trunc((p * timings.length) / 100)];
303
225
  const mode = buckets[buckets.length - 1][0];
304
226
 
305
227
  send({
@@ -310,11 +232,19 @@ export async function start(input) {
310
232
  max,
311
233
  sum,
312
234
  avg,
313
- med,
314
235
  mode,
315
- p90,
316
- p95,
317
- p99,
236
+ p1: percentile(1),
237
+ p5: percentile(5),
238
+ p10: percentile(10),
239
+ p20: percentile(20),
240
+ p33: percentile(33),
241
+ p50: percentile(50),
242
+ med: percentile(50),
243
+ p66: percentile(66),
244
+ p80: percentile(80),
245
+ p90: percentile(90),
246
+ p95: percentile(95),
247
+ p99: percentile(99),
318
248
  setup: setupMark - startMark,
319
249
  init: initDoneMark - initMark,
320
250
  cycles: teardownMark - cyclesMark,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overtake",
3
- "version": "0.0.1-rc3",
3
+ "version": "0.0.2",
4
4
  "description": "NodeJS performance benchmark",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -39,7 +39,7 @@
39
39
  "pretty-quick": "^3.1.3"
40
40
  },
41
41
  "dependencies": {
42
- "evnty": "^0.7.4",
42
+ "conode": "^0.1.0",
43
43
  "glob": "^8.0.1"
44
44
  }
45
45
  }
package/reporter.js DELETED
@@ -1,90 +0,0 @@
1
- const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏', '⠿'];
2
- const SPINNER_INTERVAL = 100;
3
- const SPINNER_PADDING = 5;
4
-
5
- const ACCURACY = 6;
6
-
7
- /** TODO
8
- * [PROGRESS] script/file.js
9
- * Test suite title
10
- * ➤ Measure
11
- * ✓ Perform
12
- * ✕ Perform
13
- * */
14
-
15
- const renderString = (message, x, direction) => {
16
- const size = message.length;
17
- process.stdout.moveCursor(0, -1);
18
- process.stdout.cursorTo(x);
19
- process.stdout.clearLine(direction);
20
- if (direction === -1) {
21
- process.stdout.cursorTo(0);
22
- }
23
- process.stdout.write(message);
24
- process.stdout.moveCursor(-process.stdout.rows, 1);
25
-
26
- return size;
27
- };
28
-
29
- const renderSpinner = (index) => renderString(SPINNER[index], 0, -1);
30
-
31
- const paddings = [];
32
-
33
- const renderMessage = (column, message) => {
34
- const padding = SPINNER_PADDING + column * 10;
35
- paddings[column + 1] = renderString(message, padding, 1);
36
- };
37
-
38
- export function formatFloat(value, digits = ACCURACY) {
39
- return parseFloat(value.toFixed(digits));
40
- }
41
-
42
- export function defaultReporter(overtake) {
43
- const spinnerSize = SPINNER.length - 1;
44
- let i = 0;
45
- let timerId = null;
46
-
47
- overtake.onLoad.on(() => {});
48
- overtake.onRun.on(() => {
49
- console.log();
50
- });
51
- overtake.onComplete.on(() => {});
52
- overtake.onScriptRegister.on(() => {});
53
- overtake.onScriptStart.on(() => {});
54
- overtake.onScriptComplete.on(() => {});
55
- overtake.onSuiteRegister.on(() => {});
56
- overtake.onSuiteStart.on(() => {});
57
- overtake.onSuiteComplete.on(() => {});
58
- overtake.onSetupRegister.on(() => {});
59
- overtake.onTeardownRegister.on(() => {});
60
- overtake.onMeasureRegister.on(() => {});
61
- overtake.onMeasureStart.on((measure) => {
62
- console.log(measure.title);
63
- });
64
- overtake.onMeasureComplete.on(() => {});
65
- overtake.onPerformRegister.on(() => {});
66
- overtake.onPerformStart.on((perform) => {
67
- console.log(perform.title);
68
- console.log();
69
- i = 0;
70
- timerId = setInterval(() => renderSpinner(i++ % spinnerSize), SPINNER_INTERVAL);
71
- });
72
- overtake.onPerformComplete.on(() => {
73
- clearInterval(timerId);
74
- renderSpinner(spinnerSize);
75
- });
76
- overtake.onPerformProgress.on(({ stage, progress }) => {
77
- renderMessage(0, stage);
78
- if (typeof progress !== 'undefined') {
79
- renderMessage(1, `${(progress * 100).toFixed(0)}%`.padStart(4, ' '));
80
- }
81
- });
82
- overtake.onReport.on((report) => {
83
- if (report.success) {
84
- renderMessage(2, `total:${report.total.toFixed(0)}ms mode:${report.mode.toFixed(ACCURACY)}ms`);
85
- } else {
86
- renderMessage(1, report.error);
87
- }
88
- console.log();
89
- });
90
- }