Skip to content

Commit 133c156

Browse files
authored
Factor out baseline calculation code from textreporter, for use in pretty and chart (#98)
* feat: fix whitespace between summaries, extract analysis code for addition to the other formatters. * feat: make sorting optional Feature parity for textReporter, but chartReporter doesn't work this way. * feat: Report baselines across text, pretty, and chart reporters.
1 parent daff350 commit 133c156

5 files changed

Lines changed: 167 additions & 109 deletions

File tree

lib/reporter/chart.js

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const { platform, arch, cpus, totalmem } = require("node:os");
22
const { styleText } = require("../utils/styleText");
3+
const { analyze } = require("../utils/baseline.js");
34

45
/**
56
* Draws a bar chart representation of a benchmark result
@@ -8,9 +9,18 @@ const { styleText } = require("../utils/styleText");
89
* @param {number} total - The maximum value in the dataset (for scaling)
910
* @param {number} samples - Number of samples collected
1011
* @param {string} metric - The metric being displayed (opsSec or totalTime)
12+
* @param {string} [comment=""] - optional additional comment
1113
* @param {number} [length=25] - Length of the bar in characters
1214
*/
13-
function drawBar(label, value, total, samples, metric, length = 25) {
15+
function drawBar(
16+
label,
17+
value,
18+
total,
19+
samples,
20+
metric,
21+
comment = "",
22+
length = 25,
23+
) {
1424
let percentage;
1525
let displayedValue;
1626
let displayedMetric;
@@ -56,11 +66,10 @@ function drawBar(label, value, total, samples, metric, length = 25) {
5666
partial +
5767
"─".repeat(length - filledLength - partial.length);
5868

59-
const displayedSamples = styleText(["yellow"], samples.toString());
69+
const displayedSamples = `${styleText(["yellow"], samples.toString().padStart(2))} samples`;
70+
const line = `${label.padEnd(45)} | ${bar} | ${displayedValue} ${displayedMetric} | ${displayedSamples} ${comment}\n`;
6071

61-
process.stdout.write(
62-
`${label.padEnd(45)} | ${bar} | ${displayedValue} ${displayedMetric} | ${displayedSamples} samples\n`,
63-
);
72+
process.stdout.write(line);
6473
}
6574

6675
const formatter = Intl.NumberFormat(undefined, {
@@ -98,13 +107,34 @@ function chartReport(results, options) {
98107
);
99108
}
100109

110+
const hasBaseline = results.find((result) => result.baseline) !== undefined;
111+
112+
results = analyze(results, false);
113+
114+
if (hasBaseline) {
115+
process.stdout.write(styleText("bold", "\nSummary (vs. baseline):\n"));
116+
}
117+
101118
for (const result of results) {
119+
let comment = "";
120+
121+
if (hasBaseline) {
122+
if (result.baseline) {
123+
comment = styleText("magenta", "(baseline)");
124+
} else if (result.comparison.startsWith("-")) {
125+
comment = styleText("red", `(${result.comparison.slice(1)}x slower)`);
126+
} else {
127+
comment = styleText("green", `(${result.comparison}x faster)`);
128+
}
129+
}
130+
102131
drawBar(
103132
result.name,
104133
result[primaryMetric],
105134
maxValue,
106135
result.histogram.samples,
107136
primaryMetric,
137+
comment,
108138
);
109139
}
110140
}

lib/reporter/pretty.js

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const { styleText } = require("../utils/styleText");
22
const { timer } = require("../clock");
33
const os = require("node:os");
4+
const { analyze } = require("../utils/baseline");
45

56
const formatter = Intl.NumberFormat(undefined, {
67
notation: "standard",
@@ -26,8 +27,8 @@ function prettyReport(results) {
2627
process.stdout.write(` OS: ${os.platform()} ${os.release()}\n`);
2728
process.stdout.write(` CPU: ${cpu.model}\n`);
2829

29-
const baselineResult = results.find((result) => result.baseline);
30-
const hasBaseline = baselineResult !== undefined;
30+
const hasBaseline = results.find((result) => result.baseline) !== undefined;
31+
3132
if (hasBaseline) {
3233
process.stdout.write(`\nLegend: ${styleText("magenta", "■")} Baseline\n\n`);
3334
}
@@ -48,11 +49,7 @@ function prettyReport(results) {
4849
if (hasBaseline) {
4950
process.stdout.write(styleText("bold", "\n\nSummary (vs. baseline):\n"));
5051

51-
const sortedResults = [...results].sort((a, b) => {
52-
if (a.baseline) return -1;
53-
if (b.baseline) return 1;
54-
return (a.opsSec || 0) - (b.opsSec || 0);
55-
});
52+
const sortedResults = analyze(results);
5653

5754
const maxNameLength = Math.max(...sortedResults.map((r) => r.name.length));
5855

@@ -65,22 +62,14 @@ function prettyReport(results) {
6562

6663
if (result.baseline) {
6764
process.stdout.write(styleText("magenta", "(baseline)"));
68-
} else if (
69-
baselineResult.opsSec !== undefined &&
70-
result.opsSec !== undefined
71-
) {
72-
const baselineHz = baselineResult.opsSec;
73-
const benchmarkHz = result.opsSec;
74-
let comparisonText;
75-
76-
if (benchmarkHz > baselineHz) {
77-
const timesFaster = (benchmarkHz / baselineHz).toFixed(2);
78-
comparisonText = styleText("green", `(${timesFaster}x faster)`);
79-
} else {
80-
const timesSlower = (baselineHz / benchmarkHz).toFixed(2);
81-
comparisonText = styleText("red", `(${timesSlower}x slower)`);
82-
}
83-
process.stdout.write(comparisonText);
65+
} else if (!result.comparison.startsWith("-")) {
66+
process.stdout.write(
67+
styleText("green", `(${result.comparison}x faster)`),
68+
);
69+
} else {
70+
process.stdout.write(
71+
styleText("red", `(${result.comparison.slice(1)}x slower)`),
72+
);
8473
}
8574
process.stdout.write("\n");
8675
}

lib/reporter/text.js

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const { styleText } = require("../utils/styleText");
22
const { timer } = require("../clock");
3+
const { analyze } = require("../utils/baseline.js");
34

45
const formatter = Intl.NumberFormat(undefined, {
56
notation: "standard",
@@ -79,12 +80,7 @@ function textReport(results) {
7980
if (baselineResult) {
8081
process.stdout.write(styleText("bold", "\nSummary (vs. baseline):\n"));
8182

82-
const sortedResults = [...results].sort((a, b) => {
83-
if (a.baseline) return -1;
84-
if (b.baseline) return 1;
85-
return (a.opsSec || 0) - (b.opsSec || 0);
86-
});
87-
83+
const sortedResults = analyze(results);
8884
const maxNameLength = Math.max(...sortedResults.map((r) => r.name.length));
8985

9086
for (const result of sortedResults) {
@@ -96,23 +92,18 @@ function textReport(results) {
9692

9793
if (result.baseline) {
9894
process.stdout.write(styleText("magenta", "(baseline)"));
99-
} else if (
100-
baselineResult.opsSec !== undefined &&
101-
result.opsSec !== undefined
102-
) {
103-
const baselineHz = baselineResult.opsSec;
104-
const benchmarkHz = result.opsSec;
105-
let comparisonText;
106-
107-
if (benchmarkHz > baselineHz) {
108-
const timesFaster = (benchmarkHz / baselineHz).toFixed(2);
109-
comparisonText = styleText("green", `(${timesFaster}x faster)`);
95+
} else if (result.comparison !== undefined) {
96+
if (!result.comparison.startsWith("-")) {
97+
process.stdout.write(
98+
styleText("green", `(${result.comparison}x faster)`),
99+
);
110100
} else {
111-
const timesSlower = (baselineHz / benchmarkHz).toFixed(2);
112-
comparisonText = styleText("red", `(${timesSlower}x slower)`);
101+
process.stdout.write(
102+
styleText("red", `(${result.comparison.slice(1)}x slower)`),
103+
);
113104
}
114-
process.stdout.write(comparisonText);
115105
}
106+
116107
process.stdout.write("\n");
117108
}
118109
}

lib/utils/baseline.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
function analyze(results, sorted = true) {
2+
const baselineResult = results.find((result) => result.baseline);
3+
4+
const output = [...results];
5+
6+
if (baselineResult?.opsSec === undefined) {
7+
return output;
8+
}
9+
10+
if (sorted) {
11+
output.sort((a, b) => {
12+
if (a.baseline) return -1;
13+
if (b.baseline) return 1;
14+
return (a.opsSec || 0) - (b.opsSec || 0);
15+
});
16+
}
17+
18+
const baselineHz = baselineResult.opsSec;
19+
20+
for (const result of output) {
21+
if (!result.baseline && result.opsSec !== undefined) {
22+
const benchmarkHz = result.opsSec;
23+
24+
if (benchmarkHz > baselineHz) {
25+
result.comparison = (benchmarkHz / baselineHz).toFixed(2);
26+
} else {
27+
result.comparison = `-${(baselineHz / benchmarkHz).toFixed(2)}`;
28+
}
29+
}
30+
}
31+
32+
return output;
33+
}
34+
35+
module.exports = {
36+
analyze,
37+
};

test/reporter.js

Lines changed: 72 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -285,19 +285,12 @@ describe("prettyReport outputs a beautiful report", async (t) => {
285285
});
286286
});
287287

288-
describe("prettyReport shows baseline comparisons", async (t) => {
289-
let output = "";
288+
describe("baseline comparisons", async (t) => {
289+
let results;
290290

291291
before(async () => {
292-
const originalStdoutWrite = process.stdout.write;
293-
process.stdout.write = (data) => {
294-
output += data;
295-
};
296-
297292
// Create a new Suite with the pretty reporter
298-
const suite = new Suite({
299-
reporter: prettyReport,
300-
});
293+
const suite = new Suite({});
301294

302295
// Add benchmarks with one being the baseline
303296
suite
@@ -315,73 +308,91 @@ describe("prettyReport shows baseline comparisons", async (t) => {
315308
});
316309

317310
// Run the suite
318-
await suite.run();
319-
320-
process.stdout.write = originalStdoutWrite;
311+
results = await suite.run();
321312
});
322313

323-
it("should include a summary section", () => {
324-
assert.ok(output.includes("Summary (vs. baseline):"));
325-
});
314+
describe("for prettyReport", async (t) => {
315+
let output = "";
326316

327-
it("should show 'faster' comparison in summary", () => {
328-
const summary = output.split("Summary (vs. baseline):")[1];
329-
assert.ok(summary.includes("faster"));
330-
});
317+
before(async () => {
318+
const originalStdoutWrite = process.stdout.write;
319+
process.stdout.write = (data) => {
320+
output += data;
321+
};
331322

332-
it("should show 'slower' comparison in summary", () => {
333-
const summary = output.split("Summary (vs. baseline):")[1];
334-
assert.ok(summary.includes("slower"));
323+
prettyReport(results);
324+
process.stdout.write = originalStdoutWrite;
325+
});
326+
327+
it("should include a summary section", () => {
328+
assert.ok(output.includes("Summary (vs. baseline):"));
329+
});
330+
331+
it("should show 'faster' comparison in summary", () => {
332+
const summary = output.split("Summary (vs. baseline):")[1];
333+
assert.ok(summary.includes("faster"));
334+
});
335+
336+
it("should show 'slower' comparison in summary", () => {
337+
const summary = output.split("Summary (vs. baseline):")[1];
338+
assert.ok(summary.includes("slower"));
339+
});
335340
});
336-
});
337341

338-
describe("textReport shows baseline comparisons", async (t) => {
339-
let output = "";
342+
describe("for textReport", async (t) => {
343+
let output = "";
340344

341-
before(async () => {
342-
const originalStdoutWrite = process.stdout.write;
343-
process.stdout.write = (data) => {
344-
output += data;
345-
};
345+
before(async () => {
346+
const originalStdoutWrite = process.stdout.write;
347+
process.stdout.write = (data) => {
348+
output += data;
349+
};
346350

347-
// Create a new Suite with the text reporter
348-
const suite = new Suite({
349-
reporter: textReport,
351+
textReport(results);
352+
process.stdout.write = originalStdoutWrite;
350353
});
351354

352-
// Add benchmarks with one being the baseline
353-
suite
354-
.add("baseline-test", { baseline: true }, () => {
355-
// Medium-speed operation
356-
for (let i = 0; i < 1000; i++) {}
357-
})
358-
.add("faster-test", () => {
359-
// Faster operation
360-
for (let i = 0; i < 100; i++) {}
361-
})
362-
.add("slower-test", () => {
363-
// Slower operation
364-
for (let i = 0; i < 10000; i++) {}
365-
});
355+
it("should include a summary section", () => {
356+
assert.ok(output.includes("Summary (vs. baseline):"));
357+
});
366358

367-
// Run the suite
368-
await suite.run();
359+
it("should show 'faster' comparison in summary", () => {
360+
const summary = output.split("Summary (vs. baseline):")[1];
361+
assert.ok(summary.includes("faster"));
362+
});
369363

370-
process.stdout.write = originalStdoutWrite;
364+
it("should show 'slower' comparison in summary", () => {
365+
const summary = output.split("Summary (vs. baseline):")[1];
366+
assert.ok(summary.includes("slower"));
367+
});
371368
});
372369

373-
it("should include a summary section", () => {
374-
assert.ok(output.includes("Summary (vs. baseline):"));
375-
});
370+
describe("for chartReport", async (t) => {
371+
let output = "";
376372

377-
it("should show 'faster' comparison in summary", () => {
378-
const summary = output.split("Summary (vs. baseline):")[1];
379-
assert.ok(summary.includes("faster"));
380-
});
373+
before(async () => {
374+
const originalStdoutWrite = process.stdout.write;
375+
process.stdout.write = (data) => {
376+
output += data;
377+
};
381378

382-
it("should show 'slower' comparison in summary", () => {
383-
const summary = output.split("Summary (vs. baseline):")[1];
384-
assert.ok(summary.includes("slower"));
379+
chartReport(results);
380+
process.stdout.write = originalStdoutWrite;
381+
});
382+
383+
it("should include a summary section", () => {
384+
assert.ok(output.includes("Summary (vs. baseline):"));
385+
});
386+
387+
it("should show 'faster' comparison in summary", () => {
388+
const summary = output.split("Summary (vs. baseline):")[1];
389+
assert.ok(summary.includes("faster"));
390+
});
391+
392+
it("should show 'slower' comparison in summary", () => {
393+
const summary = output.split("Summary (vs. baseline):")[1];
394+
assert.ok(summary.includes("slower"));
395+
});
385396
});
386397
});
387398

0 commit comments

Comments
 (0)