mirror of
https://github.com/soconnor0919/beenpad.git
synced 2026-02-05 08:16:37 -05:00
232 lines
8.2 KiB
JavaScript
232 lines
8.2 KiB
JavaScript
import fs from 'node:fs';
|
|
import { getTasks, getFullName, getTests } from '@vitest/runner/utils';
|
|
import * as pathe from 'pathe';
|
|
import c from 'tinyrainbow';
|
|
import { g as getStateSymbol, t as truncateString, F as F_RIGHT, D as DefaultReporter, f as formatProjectName, s as separator } from './index.456_DGfR.js';
|
|
import { stripVTControlCharacters } from 'node:util';
|
|
import { notNullish } from '@vitest/utils/helpers';
|
|
|
|
function createBenchmarkJsonReport(files) {
|
|
const report = { files: [] };
|
|
for (const file of files) {
|
|
const groups = [];
|
|
for (const task of getTasks(file)) if (task?.type === "suite") {
|
|
const benchmarks = [];
|
|
for (const t of task.tasks) {
|
|
const benchmark = t.meta.benchmark && t.result?.benchmark;
|
|
if (benchmark) benchmarks.push({
|
|
id: t.id,
|
|
...benchmark,
|
|
samples: []
|
|
});
|
|
}
|
|
if (benchmarks.length) groups.push({
|
|
fullName: getFullName(task, " > "),
|
|
benchmarks
|
|
});
|
|
}
|
|
report.files.push({
|
|
filepath: file.filepath,
|
|
groups
|
|
});
|
|
}
|
|
return report;
|
|
}
|
|
function flattenFormattedBenchmarkReport(report) {
|
|
const flat = {};
|
|
for (const file of report.files) for (const group of file.groups) for (const t of group.benchmarks) flat[t.id] = t;
|
|
return flat;
|
|
}
|
|
|
|
const outputMap = /* @__PURE__ */ new WeakMap();
|
|
function formatNumber(number) {
|
|
const res = String(number.toFixed(number < 100 ? 4 : 2)).split(".");
|
|
return res[0].replace(/(?=(?:\d{3})+$)\B/g, ",") + (res[1] ? `.${res[1]}` : "");
|
|
}
|
|
const tableHead = [
|
|
"name",
|
|
"hz",
|
|
"min",
|
|
"max",
|
|
"mean",
|
|
"p75",
|
|
"p99",
|
|
"p995",
|
|
"p999",
|
|
"rme",
|
|
"samples"
|
|
];
|
|
function renderBenchmarkItems(result) {
|
|
return [
|
|
result.name,
|
|
formatNumber(result.hz || 0),
|
|
formatNumber(result.min || 0),
|
|
formatNumber(result.max || 0),
|
|
formatNumber(result.mean || 0),
|
|
formatNumber(result.p75 || 0),
|
|
formatNumber(result.p99 || 0),
|
|
formatNumber(result.p995 || 0),
|
|
formatNumber(result.p999 || 0),
|
|
`±${(result.rme || 0).toFixed(2)}%`,
|
|
(result.sampleCount || 0).toString()
|
|
];
|
|
}
|
|
function computeColumnWidths(results) {
|
|
const rows = [tableHead, ...results.map((v) => renderBenchmarkItems(v))];
|
|
return Array.from(tableHead, (_, i) => Math.max(...rows.map((row) => stripVTControlCharacters(row[i]).length)));
|
|
}
|
|
function padRow(row, widths) {
|
|
return row.map((v, i) => i ? v.padStart(widths[i], " ") : v.padEnd(widths[i], " "));
|
|
}
|
|
function renderTableHead(widths) {
|
|
return " ".repeat(3) + padRow(tableHead, widths).map(c.bold).join(" ");
|
|
}
|
|
function renderBenchmark(result, widths) {
|
|
const padded = padRow(renderBenchmarkItems(result), widths);
|
|
return [
|
|
padded[0],
|
|
c.blue(padded[1]),
|
|
c.cyan(padded[2]),
|
|
c.cyan(padded[3]),
|
|
c.cyan(padded[4]),
|
|
c.cyan(padded[5]),
|
|
c.cyan(padded[6]),
|
|
c.cyan(padded[7]),
|
|
c.cyan(padded[8]),
|
|
c.dim(padded[9]),
|
|
c.dim(padded[10])
|
|
].join(" ");
|
|
}
|
|
function renderTable(options) {
|
|
const output = [];
|
|
const benchMap = {};
|
|
for (const task of options.tasks) if (task.meta.benchmark && task.result?.benchmark) benchMap[task.id] = {
|
|
current: task.result.benchmark,
|
|
baseline: options.compare?.[task.id]
|
|
};
|
|
const benchCount = Object.entries(benchMap).length;
|
|
const columnWidths = computeColumnWidths(Object.values(benchMap).flatMap((v) => [v.current, v.baseline]).filter(notNullish));
|
|
let idx = 0;
|
|
const padding = " ".repeat(1 );
|
|
for (const task of options.tasks) {
|
|
const duration = task.result?.duration;
|
|
const bench = benchMap[task.id];
|
|
let prefix = "";
|
|
if (idx === 0 && task.meta?.benchmark) prefix += `${renderTableHead(columnWidths)}\n${padding}`;
|
|
prefix += ` ${getStateSymbol(task)} `;
|
|
let suffix = "";
|
|
if (task.type === "suite") suffix += c.dim(` (${getTests(task).length})`);
|
|
if (task.mode === "skip" || task.mode === "todo") suffix += c.dim(c.gray(" [skipped]"));
|
|
if (duration != null) {
|
|
const color = duration > options.slowTestThreshold ? c.yellow : c.green;
|
|
suffix += color(` ${Math.round(duration)}${c.dim("ms")}`);
|
|
}
|
|
if (options.showHeap && task.result?.heap != null) suffix += c.magenta(` ${Math.floor(task.result.heap / 1024 / 1024)} MB heap used`);
|
|
if (bench) {
|
|
let body = renderBenchmark(bench.current, columnWidths);
|
|
if (options.compare && bench.baseline) {
|
|
if (bench.current.hz) {
|
|
const diff = bench.current.hz / bench.baseline.hz;
|
|
const diffFixed = diff.toFixed(2);
|
|
if (diffFixed === "1.0.0") body += c.gray(` [${diffFixed}x]`);
|
|
if (diff > 1) body += c.blue(` [${diffFixed}x] ⇑`);
|
|
else body += c.red(` [${diffFixed}x] ⇓`);
|
|
}
|
|
output.push(padding + prefix + body + suffix);
|
|
const bodyBaseline = renderBenchmark(bench.baseline, columnWidths);
|
|
output.push(`${padding} ${bodyBaseline} ${c.dim("(baseline)")}`);
|
|
} else {
|
|
if (bench.current.rank === 1 && benchCount > 1) body += c.bold(c.green(" fastest"));
|
|
if (bench.current.rank === benchCount && benchCount > 2) body += c.bold(c.gray(" slowest"));
|
|
output.push(padding + prefix + body + suffix);
|
|
}
|
|
} else output.push(padding + prefix + task.name + suffix);
|
|
if (task.result?.state !== "pass" && outputMap.get(task) != null) {
|
|
let data = outputMap.get(task);
|
|
if (typeof data === "string") {
|
|
data = stripVTControlCharacters(data.trim().split("\n").filter(Boolean).pop());
|
|
if (data === "") data = void 0;
|
|
}
|
|
if (data != null) {
|
|
const out = ` ${" ".repeat(options.level)}${F_RIGHT} ${data}`;
|
|
output.push(c.gray(truncateString(out, options.columns)));
|
|
}
|
|
}
|
|
idx++;
|
|
}
|
|
return output.filter(Boolean).join("\n");
|
|
}
|
|
|
|
class BenchmarkReporter extends DefaultReporter {
|
|
compare;
|
|
async onInit(ctx) {
|
|
super.onInit(ctx);
|
|
if (this.ctx.config.benchmark?.compare) {
|
|
const compareFile = pathe.resolve(this.ctx.config.root, this.ctx.config.benchmark?.compare);
|
|
try {
|
|
this.compare = flattenFormattedBenchmarkReport(JSON.parse(await fs.promises.readFile(compareFile, "utf-8")));
|
|
} catch (e) {
|
|
this.error(`Failed to read '${compareFile}'`, e);
|
|
}
|
|
}
|
|
}
|
|
onTaskUpdate(packs) {
|
|
for (const pack of packs) {
|
|
const task = this.ctx.state.idMap.get(pack[0]);
|
|
if (task?.type === "suite" && task.result?.state !== "run") task.tasks.filter((task) => task.result?.benchmark).sort((benchA, benchB) => benchA.result.benchmark.mean - benchB.result.benchmark.mean).forEach((bench, idx) => {
|
|
bench.result.benchmark.rank = Number(idx) + 1;
|
|
});
|
|
}
|
|
}
|
|
onTestSuiteResult(testSuite) {
|
|
super.onTestSuiteResult(testSuite);
|
|
this.printSuiteTable(testSuite);
|
|
}
|
|
printTestModule(testModule) {
|
|
this.printSuiteTable(testModule);
|
|
}
|
|
printSuiteTable(testTask) {
|
|
const state = testTask.state();
|
|
if (state === "pending" || state === "queued") return;
|
|
const benches = testTask.task.tasks.filter((t) => t.meta.benchmark);
|
|
const duration = testTask.task.result?.duration || 0;
|
|
if (benches.length > 0 && benches.every((t) => t.result?.state !== "run" && t.result?.state !== "queued")) {
|
|
let title = `\n ${getStateSymbol(testTask.task)} ${formatProjectName(testTask.project)}${getFullName(testTask.task, separator)}`;
|
|
if (duration != null && duration > this.ctx.config.slowTestThreshold) title += c.yellow(` ${Math.round(duration)}${c.dim("ms")}`);
|
|
this.log(title);
|
|
this.log(renderTable({
|
|
tasks: benches,
|
|
level: 1,
|
|
columns: this.ctx.logger.getColumns(),
|
|
compare: this.compare,
|
|
showHeap: this.ctx.config.logHeapUsage,
|
|
slowTestThreshold: this.ctx.config.slowTestThreshold
|
|
}));
|
|
}
|
|
}
|
|
async onTestRunEnd(testModules, unhandledErrors, reason) {
|
|
super.onTestRunEnd(testModules, unhandledErrors, reason);
|
|
// write output for future comparison
|
|
let outputFile = this.ctx.config.benchmark?.outputJson;
|
|
if (outputFile) {
|
|
outputFile = pathe.resolve(this.ctx.config.root, outputFile);
|
|
const outputDirectory = pathe.dirname(outputFile);
|
|
if (!fs.existsSync(outputDirectory)) await fs.promises.mkdir(outputDirectory, { recursive: true });
|
|
const output = createBenchmarkJsonReport(testModules.map((t) => t.task.file));
|
|
await fs.promises.writeFile(outputFile, JSON.stringify(output, null, 2));
|
|
this.log(`Benchmark report written to ${outputFile}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
class VerboseBenchmarkReporter extends BenchmarkReporter {
|
|
verbose = true;
|
|
}
|
|
|
|
const BenchmarkReportsMap = {
|
|
default: BenchmarkReporter,
|
|
verbose: VerboseBenchmarkReporter
|
|
};
|
|
|
|
export { BenchmarkReporter as B, VerboseBenchmarkReporter as V, BenchmarkReportsMap as a };
|