Skip to content

Commit cd4f96e

Browse files
H4adRafaelGSS
authored andcommitted
refactor(plugins): enable support for memory & fixes on types
1 parent abc828f commit cd4f96e

File tree

14 files changed

+193
-74
lines changed

14 files changed

+193
-74
lines changed

README.md

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -69,19 +69,32 @@ See the [examples folder](./examples/) for more common usage examples.
6969

7070
## Table of Contents
7171

72-
1. [Class `Suite`](#class-suite)
73-
1. [`suite.add()`](#suiteaddname-options-fn)
74-
2. [`suite.run()`](#suiterun)
75-
2. [Plugins](#plugins)
76-
3. [Using Reporter](#using-reporter)
77-
1. [Text Reporter](#textreport-default)
78-
2. [Chart Reporter](#chartreport)
79-
3. [Custom Reporter](#custom-reporter)
80-
4. [Setup and Teardown](#setup-and-teardown)
81-
1. [Managed Benchmarks](#managed-benchmarks)
82-
5. [Benchmark Modes](#benchmark-modes)
83-
1. [Operations Mode (Default)](#operations-mode)
84-
2. [Time Mode](#time-mode)
72+
- [Install](#install)
73+
- [Usage](#usage)
74+
- [Table of Contents](#table-of-contents)
75+
- [Sponsors](#sponsors)
76+
- [Class: `Suite`](#class-suite)
77+
- [`new Suite([options])`](#new-suiteoptions)
78+
- [`suite.add(name[, options], fn)`](#suiteaddname-options-fn)
79+
- [`suite.run()`](#suiterun)
80+
- [Plugins](#plugins)
81+
- [Plugin Methods](#plugin-methods)
82+
- [Example Plugin](#example-plugin)
83+
- [Using Reporter](#using-reporter)
84+
- [`textReport` (Default)](#textreport-default)
85+
- [`chartReport`](#chartreport)
86+
- [`htmlReport`](#htmlreport)
87+
- [`jsonReport`](#jsonreport)
88+
- [CSV Reporter](#csv-reporter)
89+
- [Pretty Reporter](#pretty-reporter)
90+
- [Custom Reporter](#custom-reporter)
91+
- [Setup and Teardown](#setup-and-teardown)
92+
- [Managed Benchmarks](#managed-benchmarks)
93+
- [Worker Threads](#worker-threads)
94+
- [Benchmark Modes](#benchmark-modes)
95+
- [Operations Mode](#operations-mode)
96+
- [Time Mode](#time-mode)
97+
- [Writing JavaScript Mistakes](#writing-javascript-mistakes)
8598

8699
## Sponsors
87100

@@ -171,8 +184,11 @@ See [Plugins](./doc/Plugins.md) for details.
171184
* `Wrapper` {string} (optional) Function to wrap the benchmark function.
172185
- **`afterClockTemplate(varNames)`**: Injects code after the benchmark finishes. Returns an array with:
173186
* `Code` {string} JavaScript code to execute.
174-
- **`onCompleteBenchmark(result)`**: Called when the benchmark completes, allowing plugins to process results.
187+
- **`onCompleteBenchmark(result, bench)`**: Called when the benchmark completes, allowing plugins to process results.
175188
- **`toString()`**: Returns a string identifier for the plugin.
189+
- **`getReport(benchmarkName)`**: Returns a string to be displayed in the benchmark result line.
190+
- **`getResult(benchmarkName)`**: Returns the data that can be used by the reporter.
191+
- **`reset()`**: Resets the plugin state, to avoid carrying over data between benchmarks.
176192

177193
### Example Plugin
178194

examples/plugins/all.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ const {
33
V8GetOptimizationStatus,
44
V8NeverOptimizePlugin,
55
V8OptimizeOnNextCallPlugin,
6+
MemoryPlugin,
67
} = require('../../lib');
78

89
const suite = new Suite({
910
plugins: [
1011
new V8GetOptimizationStatus(),
1112
new V8NeverOptimizePlugin(),
12-
// new V8OptimizeOnNextCallPlugin(),
13+
new MemoryPlugin(),
14+
new V8OptimizeOnNextCallPlugin(),
1315
],
1416
});
1517

examples/plugins/memory.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const { Suite, MemoryPlugin} = require('../../lib');
2+
3+
const suite = new Suite({
4+
plugins: [new MemoryPlugin()],
5+
});
6+
7+
suite
8+
.add(`new Uint32Array(1024)`, function () {
9+
return new Uint32Array(1024);
10+
})
11+
.add(`[Managed] new Uint32Array(1024)`, function (timer) {
12+
const assert = require('node:assert');
13+
14+
let r;
15+
16+
timer.start();
17+
for (let i = 0; i < timer.count; i++) {
18+
r = new Uint32Array(1024);
19+
}
20+
timer.end(timer.count);
21+
22+
assert.ok(r);
23+
})
24+
.run();

index.d.ts

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,36 @@
44
import type { Histogram } from "node:perf_hooks";
55

66
export declare namespace BenchNode {
7+
class Benchmark {
8+
name: string;
9+
fn: any;
10+
minTime: number;
11+
maxTime: number;
12+
plugins: Plugin[];
13+
repeatSuite: number;
14+
minSamples: number;
15+
baseline: boolean;
16+
17+
constructor(
18+
name: string,
19+
fn: any,
20+
minTime: number,
21+
maxTime: number,
22+
plugins: Plugin[],
23+
repeatSuite: number,
24+
minSamples: number,
25+
baseline?: boolean,
26+
);
27+
28+
serializeBenchmark(): void;
29+
}
30+
731
interface PluginHookVarNames {
832
awaitOrEmpty: string;
9-
bench: any; // Can be string during validation, object with 'fn' property during actual run
10-
context: any;
11-
timer: any;
33+
bench: string;
34+
context: string;
35+
timer: string;
36+
managed: boolean;
1237
}
1338

1439
interface BenchmarkResult {
@@ -44,15 +69,28 @@ export declare namespace BenchNode {
4469
count: number;
4570
}) => void | Promise<void>;
4671

72+
type OnCompleteBenchmarkResult = [
73+
duration: number,
74+
count: number,
75+
context: Record<string, any>,
76+
];
77+
type PluginResult = {
78+
type: string;
79+
[key: string]: any;
80+
};
81+
4782
interface Plugin {
4883
isSupported?(): boolean;
4984
beforeClockTemplate?(varNames: PluginHookVarNames): string[];
5085
afterClockTemplate?(varNames: PluginHookVarNames): string[];
51-
onCompleteClock?(result: BenchmarkResult): string[];
52-
onCompleteBenchmark?(result: BenchmarkResult): void;
86+
onCompleteBenchmark?(
87+
result: OnCompleteBenchmarkResult,
88+
bench: Benchmark,
89+
): void;
5390
toString?(): string;
54-
getReport?(): string;
55-
getResult?(benchmarkName: string): Record<string, any>;
91+
getReport?(benchmarkName: string): string;
92+
getResult?(benchmarkName: string): PluginResult;
93+
reset?(): void;
5694
}
5795

5896
class Suite {
@@ -66,16 +104,16 @@ export declare namespace BenchNode {
66104
isSupported(): boolean;
67105
beforeClockTemplate(varNames: PluginHookVarNames): string[];
68106
toString(): string;
69-
getReport(): string;
107+
getReport(benchmarkName: string): string;
70108
}
71109

72110
class V8GetOptimizationStatus implements Plugin {
73111
isSupported(): boolean;
74112
afterClockTemplate(varNames: PluginHookVarNames): string[];
75-
onCompleteBenchmark(result: BenchmarkResult): void;
113+
onCompleteBenchmark(result: OnCompleteBenchmarkResult): void;
76114
toString(): string;
77-
getReport(): string;
78-
getResult?(benchmarkName: string): Record<string, any>;
115+
getReport(benchmarkName: string): string;
116+
getResult?(benchmarkName: string): PluginResult;
79117
}
80118

81119
class V8OptimizeOnNextCallPlugin implements Plugin {
@@ -89,7 +127,9 @@ export declare namespace BenchNode {
89127
isSupported(): boolean;
90128
beforeClockTemplate(varNames: PluginHookVarNames): string[];
91129
afterClockTemplate(varNames: PluginHookVarNames): string[];
92-
onCompleteBenchmark?(result: BenchmarkResult): void;
130+
onCompleteBenchmark(result: OnCompleteBenchmarkResult): void;
131+
getReport(benchmarkName: string): string;
132+
getResult(benchmarkName: string): PluginResult;
93133
toString(): string;
94134
}
95135
}

lib/clock.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ function createRunUnmanagedBenchmark(bench, awaitOrEmpty) {
102102
timer: "timer",
103103
context: "context",
104104
bench: "bench",
105+
managed: false,
105106
};
106107

107108
let code = `
@@ -150,6 +151,7 @@ function createRunManagedBenchmark(bench, awaitOrEmpty) {
150151
timer: "timer",
151152
context: "context",
152153
bench: "bench",
154+
managed: true,
153155
};
154156

155157
let code = `

lib/histogram.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class StatisticalHistogram {
1212
mean;
1313
cv;
1414
stddev;
15+
finished = false;
1516

1617
/**
1718
* @returns {number[]}
@@ -49,6 +50,9 @@ class StatisticalHistogram {
4950
}
5051

5152
finish() {
53+
if (this.finished) return;
54+
55+
this.finished = true;
5256
this.removeOutliers();
5357

5458
this.calculateMinMax();

lib/lifecycle.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,11 @@ async function runBenchmark(
219219
);
220220
}
221221

222+
// since the instance is shared across benchmarks, reset it after use
223+
for (const plugin of bench.plugins) {
224+
plugin.reset?.();
225+
}
226+
222227
return result;
223228
}
224229

lib/plugins/memory.js

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
const {
2-
kStatisticalHistogramRecord,
3-
StatisticalHistogram,
4-
kStatisticalHistogramFinish,
5-
} = require("../histogram");
1+
const { StatisticalHistogram } = require("../histogram");
62

73
/**
84
* Formats a byte value into a human-readable string with appropriate units (B, Kb, MB, GB)
@@ -34,17 +30,13 @@ class MemoryPlugin {
3430
/**
3531
* @type {StatisticalHistogram}
3632
*/
37-
#heapUsedHistogram;
38-
39-
constructor() {
40-
this.reset();
41-
}
33+
#heapUsedHistogram = new StatisticalHistogram();
4234

4335
isSupported() {
4436
return typeof globalThis.gc === "function";
4537
}
4638

47-
beforeClockTemplate({ managed, globalThisVar, contextVar }) {
39+
beforeClockTemplate({ managed, context }) {
4840
if (managed && !MemoryPlugin.#WARNING_REPORTED) {
4941
MemoryPlugin.#WARNING_REPORTED = true;
5042
process.emitWarning(
@@ -54,22 +46,21 @@ class MemoryPlugin {
5446

5547
let code = "";
5648

57-
code += `${contextVar}.${MemoryPlugin.MEMORY_BEFORE_RUN} = 0;\n`;
58-
code += `${contextVar}.${MemoryPlugin.MEMORY_AFTER_RUN} = 0;\n`;
59-
code += `${globalThisVar}.gc();\n`;
60-
code += `${contextVar}.${MemoryPlugin.MEMORY_BEFORE_RUN} = ${globalThisVar}.process.memoryUsage();\n`;
49+
code += `${context}.${MemoryPlugin.MEMORY_BEFORE_RUN} = 0;\n`;
50+
code += `${context}.${MemoryPlugin.MEMORY_AFTER_RUN} = 0;\n`;
51+
code += "globalThis.gc();\n";
52+
code += `${context}.${MemoryPlugin.MEMORY_BEFORE_RUN} = globalThis.process.memoryUsage();\n`;
6153

62-
return code;
54+
return [code];
6355
}
6456

65-
afterClockTemplate({ globalThisVar, contextVar }) {
66-
return `${contextVar}.${MemoryPlugin.MEMORY_AFTER_RUN} = ${globalThisVar}.process.memoryUsage();\n`;
57+
afterClockTemplate({ context }) {
58+
return [
59+
`${context}.${MemoryPlugin.MEMORY_AFTER_RUN} = globalThis.process.memoryUsage();\n`,
60+
];
6761
}
6862

69-
onCompleteClock(result) {
70-
const realIterations = result[1];
71-
const context = result[2];
72-
63+
onCompleteBenchmark([, realIterations, context]) {
7364
const heapUsed =
7465
context[MemoryPlugin.MEMORY_AFTER_RUN].heapUsed -
7566
context[MemoryPlugin.MEMORY_BEFORE_RUN].heapUsed;
@@ -80,20 +71,16 @@ class MemoryPlugin {
8071
const memoryAllocated = (heapUsed + externalUsed) / realIterations;
8172

8273
// below 0, we just coerce to be zero
83-
this.#heapUsedHistogram[kStatisticalHistogramRecord](
84-
Math.max(0, memoryAllocated),
85-
);
86-
}
87-
88-
onCompleteBenchmark() {
89-
this.#heapUsedHistogram[kStatisticalHistogramFinish]();
74+
this.#heapUsedHistogram.record(Math.max(0, memoryAllocated));
9075
}
9176

9277
toString() {
9378
return "MemoryPlugin";
9479
}
9580

9681
getReport() {
82+
this.#heapUsedHistogram.finish();
83+
9784
return `heap usage=${formatBytes(this.#heapUsedHistogram.mean)} (${formatBytes(this.#heapUsedHistogram.min)} ... ${formatBytes(this.#heapUsedHistogram.max)})`;
9885
}
9986

@@ -104,6 +91,10 @@ class MemoryPlugin {
10491
histogram: this.#heapUsedHistogram,
10592
};
10693
}
94+
95+
reset() {
96+
this.#heapUsedHistogram = new StatisticalHistogram();
97+
}
10798
}
10899

109100
module.exports = {

lib/plugins/v8-never-opt.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class V8NeverOptimizePlugin {
99
}
1010
}
1111

12-
beforeClockTemplate(_varNames) {
12+
beforeClockTemplate() {
1313
let code = "";
1414

1515
code += `

lib/plugins/v8-print-status.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ class V8GetOptimizationStatus {
9797
optimizationStatuses: translateStatus(allAvailableStatus),
9898
};
9999
}
100+
101+
reset() {
102+
this.#optimizationStatuses = [];
103+
}
100104
}
101105

102106
module.exports = {

0 commit comments

Comments
 (0)