flexi-bench 0.0.0-alpha.4 → 0.2.0
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.
- package/CHANGELOG.md +43 -0
- package/README.md +276 -3
- package/dist/api-types.d.ts +45 -0
- package/dist/api-types.js +35 -0
- package/dist/benchmark-runner.js +5 -5
- package/dist/benchmark.d.ts +2 -1
- package/dist/benchmark.js +160 -141
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/performance-observer.js +7 -20
- package/dist/reporters/benchmark-console-reporter.d.ts +4 -1
- package/dist/reporters/benchmark-console-reporter.js +20 -7
- package/dist/reporters/composite-reporter.d.ts +21 -0
- package/dist/reporters/composite-reporter.js +29 -0
- package/dist/reporters/json-suite-reporter.d.ts +26 -0
- package/dist/reporters/json-suite-reporter.js +38 -0
- package/dist/reporters/markdown-benchmark-reporter.d.ts +13 -1
- package/dist/reporters/markdown-benchmark-reporter.js +154 -18
- package/dist/reporters/markdown-suite-reporter.d.ts +16 -0
- package/dist/reporters/markdown-suite-reporter.js +140 -0
- package/dist/reporters/noop-reporter.js +1 -3
- package/dist/reporters/suite-console-reporter.d.ts +4 -0
- package/dist/reporters/suite-console-reporter.js +24 -8
- package/dist/results.d.ts +41 -2
- package/dist/results.js +63 -6
- package/dist/shared-api.d.ts +3 -1
- package/dist/shared-api.js +10 -6
- package/dist/suite.d.ts +11 -1
- package/dist/suite.js +44 -24
- package/dist/variation.d.ts +62 -0
- package/dist/variation.js +80 -3
- package/package.json +7 -2
|
@@ -4,26 +4,162 @@ exports.MarkdownBenchmarkReporter = void 0;
|
|
|
4
4
|
const fs_1 = require("fs");
|
|
5
5
|
const markdown_factory_1 = require("markdown-factory");
|
|
6
6
|
class MarkdownBenchmarkReporter {
|
|
7
|
+
outputFile;
|
|
8
|
+
fields;
|
|
9
|
+
append;
|
|
10
|
+
isFirstReport = true;
|
|
11
|
+
accumulatedResults = new Map();
|
|
7
12
|
constructor(opts) {
|
|
8
|
-
var _a;
|
|
9
|
-
this.report = (benchmark, results) => {
|
|
10
|
-
(0, fs_1.writeFileSync)(this.outputFile, (0, markdown_factory_1.h1)(benchmark.name, results.some((r) => !!r.subresults)
|
|
11
|
-
? (0, markdown_factory_1.lines)(results.map((r) => {
|
|
12
|
-
var _a;
|
|
13
|
-
const entries = [Object.assign(Object.assign({}, r), { label: 'total' })];
|
|
14
|
-
delete entries[0].subresults;
|
|
15
|
-
for (const subresult of (_a = r.subresults) !== null && _a !== void 0 ? _a : []) {
|
|
16
|
-
entries.push(subresult);
|
|
17
|
-
}
|
|
18
|
-
return (0, markdown_factory_1.h2)(r.label, (0, markdown_factory_1.table)(entries, [
|
|
19
|
-
{ field: 'label', label: '' },
|
|
20
|
-
...this.fields,
|
|
21
|
-
]));
|
|
22
|
-
}))
|
|
23
|
-
: (0, markdown_factory_1.table)(results, [{ field: 'label', label: '' }, ...this.fields])));
|
|
24
|
-
};
|
|
25
13
|
this.outputFile = opts.outputFile;
|
|
26
|
-
this.fields =
|
|
14
|
+
this.fields = opts.fields ?? ['min', 'average', 'p95', 'max'];
|
|
15
|
+
this.append = opts.append ?? false;
|
|
16
|
+
}
|
|
17
|
+
formatDuration(value) {
|
|
18
|
+
if (value === undefined)
|
|
19
|
+
return '';
|
|
20
|
+
const totalMs = value;
|
|
21
|
+
const hours = Math.floor(totalMs / (1000 * 60 * 60));
|
|
22
|
+
const remainingAfterHours = totalMs % (1000 * 60 * 60);
|
|
23
|
+
const minutes = Math.floor(remainingAfterHours / (1000 * 60));
|
|
24
|
+
const remainingAfterMinutes = remainingAfterHours % (1000 * 60);
|
|
25
|
+
const seconds = Math.floor(remainingAfterMinutes / 1000);
|
|
26
|
+
const milliseconds = remainingAfterMinutes % 1000;
|
|
27
|
+
const parts = [];
|
|
28
|
+
if (hours > 0) {
|
|
29
|
+
parts.push(`${hours}h`);
|
|
30
|
+
if (minutes > 0)
|
|
31
|
+
parts.push(`${minutes}m`);
|
|
32
|
+
if (seconds > 0 || milliseconds > 0) {
|
|
33
|
+
const totalSeconds = seconds + milliseconds / 1000;
|
|
34
|
+
parts.push(`${totalSeconds.toFixed(1)}s`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else if (minutes > 0) {
|
|
38
|
+
parts.push(`${minutes}m`);
|
|
39
|
+
if (seconds > 0 || milliseconds > 0) {
|
|
40
|
+
const totalSeconds = seconds + milliseconds / 1000;
|
|
41
|
+
parts.push(`${totalSeconds.toFixed(1)}s`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else if (seconds > 0) {
|
|
45
|
+
const totalSeconds = seconds + milliseconds / 1000;
|
|
46
|
+
parts.push(`${totalSeconds.toFixed(1)}s`);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
parts.push(`${milliseconds.toFixed(1)}ms`);
|
|
50
|
+
}
|
|
51
|
+
return parts.join(' ');
|
|
52
|
+
}
|
|
53
|
+
report = (benchmark, results) => {
|
|
54
|
+
if (this.append) {
|
|
55
|
+
// In append mode, accumulate results and write incrementally
|
|
56
|
+
this.accumulatedResults.set(benchmark.name, results);
|
|
57
|
+
const content = this.generateBenchmarkContent(benchmark, results);
|
|
58
|
+
if (this.isFirstReport) {
|
|
59
|
+
// First report: clear the file and write header
|
|
60
|
+
(0, fs_1.writeFileSync)(this.outputFile, content);
|
|
61
|
+
this.isFirstReport = false;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// Subsequent reports: append with a separator
|
|
65
|
+
(0, fs_1.appendFileSync)(this.outputFile, '\n\n---\n\n' + content);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// Legacy mode: overwrite file (useful for single benchmark reporting)
|
|
70
|
+
(0, fs_1.writeFileSync)(this.outputFile, this.generateBenchmarkContent(benchmark, results));
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
generateBenchmarkContent(benchmark, results) {
|
|
74
|
+
const sections = [];
|
|
75
|
+
// Main results section
|
|
76
|
+
sections.push(this.generateResultsTable(results));
|
|
77
|
+
// Comparison section (if multiple variations)
|
|
78
|
+
if (results.length > 1) {
|
|
79
|
+
sections.push(this.generateComparison(results));
|
|
80
|
+
}
|
|
81
|
+
return (0, markdown_factory_1.h1)(benchmark.name, (0, markdown_factory_1.lines)(sections));
|
|
82
|
+
}
|
|
83
|
+
generateResultsTable(results) {
|
|
84
|
+
const fieldConfigs = [
|
|
85
|
+
{ field: 'label', label: '' },
|
|
86
|
+
...this.fields.map((field) => ({
|
|
87
|
+
field: field,
|
|
88
|
+
label: field,
|
|
89
|
+
mapFn: (item) => {
|
|
90
|
+
const value = item[field];
|
|
91
|
+
if (field === 'iterations') {
|
|
92
|
+
return String(value ?? '');
|
|
93
|
+
}
|
|
94
|
+
if (typeof value === 'number') {
|
|
95
|
+
return this.formatDuration(value);
|
|
96
|
+
}
|
|
97
|
+
return String(value ?? '');
|
|
98
|
+
},
|
|
99
|
+
})),
|
|
100
|
+
];
|
|
101
|
+
if (results.some((r) => !!r.subresults)) {
|
|
102
|
+
return (0, markdown_factory_1.lines)(results.map((r) => {
|
|
103
|
+
const entries = [{ ...r, label: 'total' }];
|
|
104
|
+
delete entries[0].subresults;
|
|
105
|
+
for (const subresult of r.subresults ?? []) {
|
|
106
|
+
entries.push(subresult);
|
|
107
|
+
}
|
|
108
|
+
return (0, markdown_factory_1.h2)(r.label, (0, markdown_factory_1.table)(entries, fieldConfigs));
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
return (0, markdown_factory_1.table)(results, fieldConfigs);
|
|
112
|
+
}
|
|
113
|
+
generateComparison(results) {
|
|
114
|
+
const sorted = [...results].sort((a, b) => a.average - b.average);
|
|
115
|
+
const fastest = sorted[0];
|
|
116
|
+
const hasVaryingIterations = results.some((r) => r.iterations !== results[0].iterations);
|
|
117
|
+
const entries = sorted.map((result, index) => {
|
|
118
|
+
const entry = {
|
|
119
|
+
label: result.label,
|
|
120
|
+
indicator: index === 0 ? '🏆' : '',
|
|
121
|
+
};
|
|
122
|
+
for (const field of this.fields) {
|
|
123
|
+
if (field === 'iterations') {
|
|
124
|
+
if (hasVaryingIterations) {
|
|
125
|
+
entry.iterations = String(result.iterations);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
const value = result[field];
|
|
130
|
+
const fastestValue = fastest[field];
|
|
131
|
+
if (index === 0) {
|
|
132
|
+
entry[field] = 'baseline';
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
const factor = value / fastestValue;
|
|
136
|
+
entry[field] = `${factor.toFixed(2)}x slower`;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return entry;
|
|
141
|
+
});
|
|
142
|
+
const columns = [
|
|
143
|
+
{ field: 'label', label: 'Variation' },
|
|
144
|
+
...this.fields
|
|
145
|
+
.filter((field) => field !== 'iterations' || hasVaryingIterations)
|
|
146
|
+
.map((field) => ({ field, label: field })),
|
|
147
|
+
{ field: 'indicator', label: '' },
|
|
148
|
+
];
|
|
149
|
+
const header = hasVaryingIterations
|
|
150
|
+
? 'Comparison'
|
|
151
|
+
: `Comparison (${results[0].iterations} iterations)`;
|
|
152
|
+
return (0, markdown_factory_1.h3)(header, (0, markdown_factory_1.table)(entries.map((e) => ({ ...e })), columns));
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Clears the output file. Useful when re-running benchmarks.
|
|
156
|
+
*/
|
|
157
|
+
clear() {
|
|
158
|
+
if ((0, fs_1.existsSync)(this.outputFile)) {
|
|
159
|
+
(0, fs_1.unlinkSync)(this.outputFile);
|
|
160
|
+
}
|
|
161
|
+
this.isFirstReport = true;
|
|
162
|
+
this.accumulatedResults.clear();
|
|
27
163
|
}
|
|
28
164
|
}
|
|
29
165
|
exports.MarkdownBenchmarkReporter = MarkdownBenchmarkReporter;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SuiteReporter } from '../api-types';
|
|
2
|
+
import { Result } from '../results';
|
|
3
|
+
export declare class MarkdownSuiteReporter implements SuiteReporter {
|
|
4
|
+
outputFile: string;
|
|
5
|
+
title: string;
|
|
6
|
+
fields: Array<keyof Omit<Result, 'subresults' | 'label' | 'raw'>>;
|
|
7
|
+
constructor(opts: {
|
|
8
|
+
outputFile: string;
|
|
9
|
+
title?: string;
|
|
10
|
+
fields?: MarkdownSuiteReporter['fields'];
|
|
11
|
+
});
|
|
12
|
+
private formatDuration;
|
|
13
|
+
report: (results: Record<string, Result[]>) => void;
|
|
14
|
+
private renderBenchmarkSection;
|
|
15
|
+
private generateComparison;
|
|
16
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MarkdownSuiteReporter = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const markdown_factory_1 = require("markdown-factory");
|
|
6
|
+
class MarkdownSuiteReporter {
|
|
7
|
+
outputFile;
|
|
8
|
+
title;
|
|
9
|
+
fields;
|
|
10
|
+
constructor(opts) {
|
|
11
|
+
this.outputFile = opts.outputFile;
|
|
12
|
+
this.title = opts.title ?? 'Benchmark Results';
|
|
13
|
+
this.fields = opts.fields ?? ['min', 'average', 'p95', 'max'];
|
|
14
|
+
}
|
|
15
|
+
formatDuration(value) {
|
|
16
|
+
if (value === undefined)
|
|
17
|
+
return '';
|
|
18
|
+
const totalMs = value;
|
|
19
|
+
const hours = Math.floor(totalMs / (1000 * 60 * 60));
|
|
20
|
+
const remainingAfterHours = totalMs % (1000 * 60 * 60);
|
|
21
|
+
const minutes = Math.floor(remainingAfterHours / (1000 * 60));
|
|
22
|
+
const remainingAfterMinutes = remainingAfterHours % (1000 * 60);
|
|
23
|
+
const seconds = Math.floor(remainingAfterMinutes / 1000);
|
|
24
|
+
const milliseconds = remainingAfterMinutes % 1000;
|
|
25
|
+
const parts = [];
|
|
26
|
+
if (hours > 0) {
|
|
27
|
+
parts.push(`${hours}h`);
|
|
28
|
+
if (minutes > 0)
|
|
29
|
+
parts.push(`${minutes}m`);
|
|
30
|
+
if (seconds > 0 || milliseconds > 0) {
|
|
31
|
+
const totalSeconds = seconds + milliseconds / 1000;
|
|
32
|
+
parts.push(`${totalSeconds.toFixed(1)}s`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (minutes > 0) {
|
|
36
|
+
parts.push(`${minutes}m`);
|
|
37
|
+
if (seconds > 0 || milliseconds > 0) {
|
|
38
|
+
const totalSeconds = seconds + milliseconds / 1000;
|
|
39
|
+
parts.push(`${totalSeconds.toFixed(1)}s`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (seconds > 0) {
|
|
43
|
+
const totalSeconds = seconds + milliseconds / 1000;
|
|
44
|
+
parts.push(`${totalSeconds.toFixed(1)}s`);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
parts.push(`${milliseconds.toFixed(1)}ms`);
|
|
48
|
+
}
|
|
49
|
+
return parts.join(' ');
|
|
50
|
+
}
|
|
51
|
+
report = (results) => {
|
|
52
|
+
const benchmarkSections = Object.entries(results).map(([benchmarkName, benchmarkResults]) => {
|
|
53
|
+
return this.renderBenchmarkSection(benchmarkName, benchmarkResults);
|
|
54
|
+
});
|
|
55
|
+
(0, fs_1.writeFileSync)(this.outputFile, (0, markdown_factory_1.h1)(this.title, (0, markdown_factory_1.lines)(benchmarkSections)));
|
|
56
|
+
};
|
|
57
|
+
renderBenchmarkSection(benchmarkName, results) {
|
|
58
|
+
const hasSubresults = results.some((r) => !!r.subresults);
|
|
59
|
+
const hasMultipleVariations = results.length > 1;
|
|
60
|
+
const sections = [];
|
|
61
|
+
const fieldConfigs = [
|
|
62
|
+
{ field: 'label', label: '' },
|
|
63
|
+
...this.fields.map((field) => ({
|
|
64
|
+
field: field,
|
|
65
|
+
label: field,
|
|
66
|
+
mapFn: (item) => {
|
|
67
|
+
const value = item[field];
|
|
68
|
+
if (field === 'iterations') {
|
|
69
|
+
return String(value ?? '');
|
|
70
|
+
}
|
|
71
|
+
if (typeof value === 'number') {
|
|
72
|
+
return this.formatDuration(value);
|
|
73
|
+
}
|
|
74
|
+
return String(value ?? '');
|
|
75
|
+
},
|
|
76
|
+
})),
|
|
77
|
+
];
|
|
78
|
+
// Main results table
|
|
79
|
+
if (hasSubresults) {
|
|
80
|
+
sections.push((0, markdown_factory_1.lines)(results.map((r) => {
|
|
81
|
+
const entries = [{ ...r, label: 'total' }];
|
|
82
|
+
delete entries[0].subresults;
|
|
83
|
+
for (const subresult of r.subresults ?? []) {
|
|
84
|
+
entries.push(subresult);
|
|
85
|
+
}
|
|
86
|
+
return (0, markdown_factory_1.h2)(r.label, (0, markdown_factory_1.table)(entries, fieldConfigs));
|
|
87
|
+
})));
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
sections.push((0, markdown_factory_1.table)(results, fieldConfigs));
|
|
91
|
+
}
|
|
92
|
+
// Comparison section (if multiple variations)
|
|
93
|
+
if (hasMultipleVariations) {
|
|
94
|
+
sections.push(this.generateComparison(results));
|
|
95
|
+
}
|
|
96
|
+
return (0, markdown_factory_1.h2)(benchmarkName, (0, markdown_factory_1.lines)(sections));
|
|
97
|
+
}
|
|
98
|
+
generateComparison(results) {
|
|
99
|
+
const sorted = [...results].sort((a, b) => a.average - b.average);
|
|
100
|
+
const fastest = sorted[0];
|
|
101
|
+
const hasVaryingIterations = results.some((r) => r.iterations !== results[0].iterations);
|
|
102
|
+
const entries = sorted.map((result, index) => {
|
|
103
|
+
const entry = {
|
|
104
|
+
label: result.label,
|
|
105
|
+
indicator: index === 0 ? '🏆' : '',
|
|
106
|
+
};
|
|
107
|
+
for (const field of this.fields) {
|
|
108
|
+
if (field === 'iterations') {
|
|
109
|
+
if (hasVaryingIterations) {
|
|
110
|
+
entry.iterations = String(result.iterations);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
const value = result[field];
|
|
115
|
+
const fastestValue = fastest[field];
|
|
116
|
+
if (index === 0) {
|
|
117
|
+
entry[field] = 'baseline';
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
const factor = value / fastestValue;
|
|
121
|
+
entry[field] = `${factor.toFixed(2)}x slower`;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return entry;
|
|
126
|
+
});
|
|
127
|
+
const columns = [
|
|
128
|
+
{ field: 'label', label: 'Variation' },
|
|
129
|
+
...this.fields
|
|
130
|
+
.filter((field) => field !== 'iterations' || hasVaryingIterations)
|
|
131
|
+
.map((field) => ({ field, label: field })),
|
|
132
|
+
{ field: 'indicator', label: '' },
|
|
133
|
+
];
|
|
134
|
+
const header = hasVaryingIterations
|
|
135
|
+
? 'Comparison'
|
|
136
|
+
: `Comparison (${results[0].iterations} iterations)`;
|
|
137
|
+
return (0, markdown_factory_1.h3)(header, (0, markdown_factory_1.table)(entries.map((e) => ({ ...e })), columns));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
exports.MarkdownSuiteReporter = MarkdownSuiteReporter;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { SuiteReporter } from '../api-types';
|
|
2
2
|
import { Result } from '../results';
|
|
3
3
|
export declare class SuiteConsoleReporter implements SuiteReporter {
|
|
4
|
+
private noColor;
|
|
5
|
+
constructor(opts?: {
|
|
6
|
+
noColor?: boolean;
|
|
7
|
+
});
|
|
4
8
|
report: (results: Record<string, Result[]>) => void;
|
|
5
9
|
}
|
|
@@ -1,15 +1,31 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SuiteConsoleReporter = void 0;
|
|
4
|
+
function getNoColorOption(explicitNoColor) {
|
|
5
|
+
if (explicitNoColor !== undefined) {
|
|
6
|
+
return explicitNoColor;
|
|
7
|
+
}
|
|
8
|
+
return process.env.NO_COLOR !== undefined && process.env.NO_COLOR !== '';
|
|
9
|
+
}
|
|
4
10
|
class SuiteConsoleReporter {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
for (const [name, result] of Object.entries(results)) {
|
|
9
|
-
console.log(`Benchmark: ${name}`);
|
|
10
|
-
console.table(result);
|
|
11
|
-
}
|
|
12
|
-
};
|
|
11
|
+
noColor;
|
|
12
|
+
constructor(opts) {
|
|
13
|
+
this.noColor = getNoColorOption(opts?.noColor);
|
|
13
14
|
}
|
|
15
|
+
report = (results) => {
|
|
16
|
+
if (this.noColor) {
|
|
17
|
+
console.log('Suite Results:');
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
console.log('\x1b[1mSuite Results:\x1b[0m');
|
|
21
|
+
}
|
|
22
|
+
for (const [name, result] of Object.entries(results)) {
|
|
23
|
+
const tableEntries = result.map(({ raw, ...rest }) => ({
|
|
24
|
+
...rest,
|
|
25
|
+
}));
|
|
26
|
+
console.log(`Benchmark: ${name}`);
|
|
27
|
+
console.table(tableEntries);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
14
30
|
}
|
|
15
31
|
exports.SuiteConsoleReporter = SuiteConsoleReporter;
|
package/dist/results.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
export type GitInfo = {
|
|
2
|
+
head: string;
|
|
3
|
+
sha: string;
|
|
4
|
+
dirty: boolean;
|
|
5
|
+
};
|
|
1
6
|
/**
|
|
2
7
|
* A measurement result.
|
|
3
8
|
*/
|
|
@@ -25,10 +30,44 @@ export type Result = {
|
|
|
25
30
|
/**
|
|
26
31
|
* The raw durations, in order. Used for custom reporters.
|
|
27
32
|
*/
|
|
28
|
-
raw: number[];
|
|
33
|
+
raw: (number | Error)[];
|
|
34
|
+
/**
|
|
35
|
+
* Whether any run of the benchmark failed.
|
|
36
|
+
*/
|
|
37
|
+
failed?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* The rate of failure, if any.
|
|
40
|
+
*/
|
|
41
|
+
failureRate?: number;
|
|
29
42
|
/**
|
|
30
43
|
* Subresults, if any. Typically sourced from performance observer.
|
|
31
44
|
*/
|
|
32
45
|
subresults?: Result[];
|
|
46
|
+
/**
|
|
47
|
+
* The number of iterations that were run.
|
|
48
|
+
*/
|
|
49
|
+
iterations?: number;
|
|
50
|
+
/**
|
|
51
|
+
* The total wall-clock duration for all iterations in milliseconds.
|
|
52
|
+
*/
|
|
53
|
+
totalDuration?: number;
|
|
54
|
+
/**
|
|
55
|
+
* The name of the benchmark that produced this result.
|
|
56
|
+
*/
|
|
57
|
+
benchmarkName?: string;
|
|
58
|
+
/**
|
|
59
|
+
* The name of the variation that produced this result.
|
|
60
|
+
*/
|
|
61
|
+
variationName?: string;
|
|
62
|
+
/**
|
|
63
|
+
* Git information about the repository state when this result was produced.
|
|
64
|
+
*/
|
|
65
|
+
git?: GitInfo;
|
|
33
66
|
};
|
|
34
|
-
export declare function
|
|
67
|
+
export declare function getGitInfo(): GitInfo | undefined;
|
|
68
|
+
export declare function calculateResultsFromDurations(label: string, durations: (number | Error)[], metadata?: {
|
|
69
|
+
iterations?: number;
|
|
70
|
+
totalDuration?: number;
|
|
71
|
+
benchmarkName?: string;
|
|
72
|
+
variationName?: string;
|
|
73
|
+
}): Result;
|
package/dist/results.js
CHANGED
|
@@ -1,14 +1,71 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getGitInfo = getGitInfo;
|
|
3
7
|
exports.calculateResultsFromDurations = calculateResultsFromDurations;
|
|
4
|
-
|
|
5
|
-
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const is_ci_1 = __importDefault(require("is-ci"));
|
|
10
|
+
let cachedGitInfo = null;
|
|
11
|
+
function getGitInfo() {
|
|
12
|
+
if (cachedGitInfo !== null) {
|
|
13
|
+
return cachedGitInfo;
|
|
14
|
+
}
|
|
15
|
+
if (!is_ci_1.default) {
|
|
16
|
+
cachedGitInfo = undefined;
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const getGitOutput = (args) => {
|
|
20
|
+
try {
|
|
21
|
+
const result = (0, child_process_1.spawn)('git', args, {
|
|
22
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
23
|
+
});
|
|
24
|
+
let output = '';
|
|
25
|
+
result.stdout?.on('data', (data) => {
|
|
26
|
+
output += data.toString();
|
|
27
|
+
});
|
|
28
|
+
result.stdout?.on('end', () => {
|
|
29
|
+
return output.trim();
|
|
30
|
+
});
|
|
31
|
+
return output.trim();
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return '';
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const head = getGitOutput(['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
38
|
+
const sha = getGitOutput(['rev-parse', 'HEAD']);
|
|
39
|
+
const dirty = getGitOutput(['status', '--porcelain']).length > 0;
|
|
40
|
+
cachedGitInfo = {
|
|
41
|
+
head,
|
|
42
|
+
sha,
|
|
43
|
+
dirty,
|
|
44
|
+
};
|
|
45
|
+
return cachedGitInfo;
|
|
46
|
+
}
|
|
47
|
+
function calculateResultsFromDurations(label, durations, metadata) {
|
|
48
|
+
const errors = [];
|
|
49
|
+
const results = [];
|
|
50
|
+
for (const duration of durations) {
|
|
51
|
+
if (duration instanceof Error) {
|
|
52
|
+
errors.push(duration);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
results.push(duration);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
results.sort((a, b) => a - b);
|
|
6
59
|
return {
|
|
7
60
|
label,
|
|
8
|
-
min: Math.min(...
|
|
9
|
-
max: Math.max(...
|
|
10
|
-
average:
|
|
11
|
-
p95:
|
|
61
|
+
min: Math.min(...results),
|
|
62
|
+
max: Math.max(...results),
|
|
63
|
+
average: results.reduce((acc, duration) => acc + duration, 0) / results.length,
|
|
64
|
+
p95: results[Math.floor(results.length * 0.95)],
|
|
12
65
|
raw: durations,
|
|
66
|
+
failed: errors.length > 0,
|
|
67
|
+
failureRate: errors.length / durations.length,
|
|
68
|
+
...metadata,
|
|
69
|
+
...(getGitInfo() ? { git: getGitInfo() } : {}),
|
|
13
70
|
};
|
|
14
71
|
}
|
package/dist/shared-api.d.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { Action, SetupMethod, TeardownMethod } from './api-types';
|
|
1
|
+
import { Action, ErrorStrategy, SetupMethod, TeardownMethod } from './api-types';
|
|
2
2
|
export declare class BenchmarkBase {
|
|
3
3
|
setupEachMethods: SetupMethod[];
|
|
4
4
|
setupMethods: SetupMethod[];
|
|
5
5
|
teardownMethods: TeardownMethod[];
|
|
6
6
|
teardownEachMethods: TeardownMethod[];
|
|
7
7
|
action?: Action;
|
|
8
|
+
errorStrategy?: ErrorStrategy;
|
|
8
9
|
withSetup(setup: SetupMethod): this;
|
|
9
10
|
withSetupEach(setup: SetupMethod): this;
|
|
10
11
|
withTeardown(teardown: TeardownMethod): this;
|
|
11
12
|
withTeardownEach(teardown: TeardownMethod): this;
|
|
12
13
|
withAction(action: Action): this;
|
|
14
|
+
withErrorStrategy(errorStrategy: ErrorStrategy): this;
|
|
13
15
|
}
|
package/dist/shared-api.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.BenchmarkBase = void 0;
|
|
4
4
|
class BenchmarkBase {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
setupEachMethods = [];
|
|
6
|
+
setupMethods = [];
|
|
7
|
+
teardownMethods = [];
|
|
8
|
+
teardownEachMethods = [];
|
|
9
|
+
action;
|
|
10
|
+
errorStrategy;
|
|
11
11
|
withSetup(setup) {
|
|
12
12
|
this.setupMethods.push(setup);
|
|
13
13
|
return this;
|
|
@@ -28,5 +28,9 @@ class BenchmarkBase {
|
|
|
28
28
|
this.action = action;
|
|
29
29
|
return this;
|
|
30
30
|
}
|
|
31
|
+
withErrorStrategy(errorStrategy) {
|
|
32
|
+
this.errorStrategy = errorStrategy;
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
31
35
|
}
|
|
32
36
|
exports.BenchmarkBase = BenchmarkBase;
|
package/dist/suite.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BenchmarkReporter, SuiteReporter } from './api-types';
|
|
1
|
+
import { BenchmarkReporter, ErrorStrategy, SuiteReporter } from './api-types';
|
|
2
2
|
import { Benchmark } from './benchmark';
|
|
3
3
|
import { Result } from './results';
|
|
4
4
|
import { Variation } from './variation';
|
|
@@ -11,11 +11,21 @@ export declare class Suite {
|
|
|
11
11
|
private variations;
|
|
12
12
|
private reporter;
|
|
13
13
|
private benchmarkReporter?;
|
|
14
|
+
private errorStrategy;
|
|
15
|
+
private shouldSetErrorStrategyOnBenchmarks;
|
|
14
16
|
constructor(name: string, options?: SuiteOptions);
|
|
15
17
|
addBenchmark(benchmark: Benchmark): this;
|
|
16
18
|
withVariation(variation: Variation): this;
|
|
17
19
|
withVariations(variations: Variation[]): this;
|
|
18
20
|
withReporter(reporter: SuiteReporter): this;
|
|
19
21
|
withBenchmarkReporter(reporter: BenchmarkReporter): this;
|
|
22
|
+
/**
|
|
23
|
+
* Sets the error strategy for the suite and optionally for the benchmarks.
|
|
24
|
+
* @param errorStrategy Determines what to do when an error occurs during a benchmark run. See {@link ErrorStrategy}
|
|
25
|
+
* @param opts Options for the error strategy. If `shouldSetOnBenchmarks` is true, the error strategy will be set on all benchmarks in the suite.
|
|
26
|
+
*/
|
|
27
|
+
withErrorStrategy(errorStrategy: ErrorStrategy, opts?: {
|
|
28
|
+
shouldSetOnBenchmarks?: boolean;
|
|
29
|
+
}): this;
|
|
20
30
|
run(): Promise<Record<string, Result[]>>;
|
|
21
31
|
}
|