From 69f9a2c373a0db851a7a0603c2db0d7878d0fdbe Mon Sep 17 00:00:00 2001 From: Andre Wiggins Date: Thu, 29 Feb 2024 12:11:40 -0800 Subject: [PATCH 1/2] Clean up code a bit --- apps/table-app/preact-class/index.jsx | 4 ++-- cli/src/format.js | 2 +- cli/src/results.js | 7 +++---- cli/src/tach.js | 23 ----------------------- 4 files changed, 6 insertions(+), 30 deletions(-) diff --git a/apps/table-app/preact-class/index.jsx b/apps/table-app/preact-class/index.jsx index 95e1e5f..2a33cf8 100644 --- a/apps/table-app/preact-class/index.jsx +++ b/apps/table-app/preact-class/index.jsx @@ -36,7 +36,7 @@ class Row extends Component { } render() { - let { styleClass, onClick, onDelete, data } = this.props; + let { styleClass, data } = this.props; return ( @@ -120,7 +120,7 @@ export class Main extends Component { + /> ); } diff --git a/cli/src/format.js b/cli/src/format.js index 61ad3b9..ff58179 100644 --- a/cli/src/format.js +++ b/cli/src/format.js @@ -50,7 +50,7 @@ const createMeanValueDimension = (format) => ({ }); /** @type {(benchmark: BenchmarkResult[]) => any} */ -export function buildTable(benchmarkResults) { +export function displayTable(benchmarkResults) { for (let benchmarkResult of benchmarkResults) { let formatNum = milli; if (benchmarkResult.measurement.name === "usedJSHeapSize") { diff --git a/cli/src/results.js b/cli/src/results.js index e3bc7a8..7b4d3aa 100644 --- a/cli/src/results.js +++ b/cli/src/results.js @@ -1,4 +1,4 @@ -import { buildTable } from "./format.js"; +import { displayTable } from "./format.js"; import { computeStats } from "./stats.js"; import { parseBenchmarkId } from "./utils.js"; @@ -14,7 +14,8 @@ export async function displayResults(tachResults) { return; } - const result = buildBenchmarkResults(tachResults); + const results = buildBenchmarkResults(tachResults); + displayTable(results); } /** @type {(tachResults: TachResult[]) => BenchmarkResult[]} */ @@ -60,7 +61,5 @@ function buildBenchmarkResults(tachResults) { computeStats(results); - buildTable(results); - return results; } diff --git a/cli/src/tach.js b/cli/src/tach.js index 241eabe..50fd2c7 100644 --- a/cli/src/tach.js +++ b/cli/src/tach.js @@ -1,5 +1,4 @@ import { writeFile, mkdir } from "node:fs/promises"; -import { createRequire } from "node:module"; import * as path from "node:path"; import { deleteAsync } from "del"; import { main } from "tachometer"; @@ -13,8 +12,6 @@ import { resultsPath, } from "./utils.js"; -const require = createRequire(import.meta.url); - const measureName = "duration"; // Must match measureName in '../src/util.js' const TACH_SCHEMA = "https://raw.githubusercontent.com/Polymer/tachometer/master/config.schema.json"; @@ -131,26 +128,6 @@ async function generateTachConfig(benchmarkFile, benchConfig) { return { name: baseName, configPath: tachConfigPath, config: tachConfig }; } -/** - * @param {import('child_process').ChildProcess} childProcess - * @returns {Promise} - */ -async function waitForExit(childProcess) { - return new Promise((resolve, reject) => { - childProcess.once("exit", (code, signal) => { - if (code === 0 || signal == "SIGINT") { - resolve(); - } else { - reject(new Error("Exit with error code: " + code)); - } - }); - - childProcess.once("error", (err) => { - reject(err); - }); - }); -} - /** @type {(benchmarkFile: string, benchConfig: BenchmarkConfig) => Promise} */ export async function runTach(benchmarkFile, benchConfig) { const { name, configPath } = await generateTachConfig( From 4da432d276c97a808d7efdbc18ad7c0c8e7aa1e8 Mon Sep 17 00:00:00 2001 From: Andre Wiggins Date: Thu, 29 Feb 2024 14:42:25 -0800 Subject: [PATCH 2/2] Combine format and result files --- cli/src/format.js | 199 ------------------------------------------- cli/src/results.js | 205 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 203 insertions(+), 201 deletions(-) delete mode 100644 cli/src/format.js diff --git a/cli/src/format.js b/cli/src/format.js deleted file mode 100644 index ff58179..0000000 --- a/cli/src/format.js +++ /dev/null @@ -1,199 +0,0 @@ -import kleur from "kleur"; -import table from "table"; -import { makeBenchmarkLabel, makeDepGroupLabel } from "./utils.js"; - -/** @type {(benchmarkResult: BenchmarkResult, fixedDimensions: Dimension[]) => string} */ -function getTableTitle(benchmarkResult, fixedDimensions) { - const benchmarkName = benchmarkResult.benchName; - const browser = benchmarkResult.browser; - const measurementName = benchmarkResult.measurement.name ?? ""; - - const browserName = `${browser.name}${browser.headless ? "-headless" : ""}`; - const firstVariation = benchmarkResult.variations[0]; - const details = [ - browserName, - ...fixedDimensions.map((d) => d.format(firstVariation)), - ]; - - return `${kleur.bold(benchmarkName)} - ${kleur.bold(measurementName)} (${details.join("; ")})`; -} - -/** @type {Dimension} */ -const sampleSizeDimension = { - format: (r) => r.samples.length.toString(), - label: "Sample Size", -}; - -/** @type {Dimension} */ -const implementationDimension = { - format: (r) => r.implementation, - label: "Implementation", -}; - -/** @type {Dimension} */ -const depGroupDimension = { - format: (r) => makeDepGroupLabel(r.dependencies), - label: "Dependency Group", -}; - -/** @type {(format: (n: number) => string) => Dimension} */ -const createMeanValueDimension = (format) => ({ - format: (r) => { - return [ - formatConfidenceInterval(r.stats.meanCI, format), - "", - r.stats.histogram, - ].join("\n"); - }, - label: "Mean", - tableConfig: { alignment: "right" }, -}); - -/** @type {(benchmark: BenchmarkResult[]) => any} */ -export function displayTable(benchmarkResults) { - for (let benchmarkResult of benchmarkResults) { - let formatNum = milli; - if (benchmarkResult.measurement.name === "usedJSHeapSize") { - formatNum = megabytes; - } - - const sampleSizeSet = new Set( - benchmarkResult.variations.map((r) => r.samples.length), - ); - const implSet = new Set( - benchmarkResult.variations.map((r) => r.implementation), - ); - const depGroupSet = new Set( - benchmarkResult.variations.map((r) => r.depGroupId), - ); - - const fixedDimensions = [ - ...(sampleSizeSet.size === 1 ? [sampleSizeDimension] : []), - ...(implSet.size === 1 ? [implementationDimension] : []), - ...(depGroupSet.size === 1 ? [depGroupDimension] : []), - ]; - - const varyingDimensions = [ - ...(implSet.size === 1 ? [] : [implementationDimension]), - ...(depGroupSet.size === 1 ? [] : [depGroupDimension]), - ...(sampleSizeSet.size === 1 ? [] : [sampleSizeDimension]), - ]; - - /** @type {Dimension[]} */ - const vsDimensions = []; - if (benchmarkResult.variations.length > 1) { - const includeDepGroup = depGroupSet.size > 1; - const includeImpl = implSet.size > 1; - - for (let i = 0; i < benchmarkResult.variations.length; i++) { - const variation = benchmarkResult.variations[i]; - - vsDimensions.push({ - label: - "vs " + - makeBenchmarkLabel( - includeImpl ? variation.implementation : null, - includeDepGroup ? variation.dependencies : null, - ), - format: (r) => { - const diff = r.differences[i]; - if (!diff) return kleur.gray("\n-"); - return formatDifference(diff, formatNum); - }, - tableConfig: { alignment: "right" }, - }); - } - } - - /** @type {Dimension[]} */ - const tableDimensions = [ - ...varyingDimensions, - createMeanValueDimension(formatNum), - ...vsDimensions, - ]; - - console.log(getTableTitle(benchmarkResult, fixedDimensions)); - console.log( - horizontalTerminalTable(tableDimensions, benchmarkResult.variations), - ); - } -} - -/** @type {(dimensions: Dimension[], results: VariationResult[]) => string} */ -function horizontalTerminalTable(dimensions, results) { - const rows = [ - dimensions.map((d) => kleur.bold(d.label)), - ...results.map((r) => dimensions.map((d) => d.format(r))), - ]; - - return table.table(rows, { - border: table.getBorderCharacters("norc"), - columns: dimensions.map((d) => d.tableConfig ?? {}), - }); -} - -/** - * Format a confidence interval as "[low, high]". - * @type {(ci: ConfidenceInterval, format: (n: number) => string) => string} - */ -function formatConfidenceInterval(ci, format) { - return `${format(ci.low)} ${kleur.gray("-")} ${format(ci.high)}`; -} - -/** - * Prefix positive numbers with a red "+" and negative ones with a green "-". - * @type {(n: number, format: (n: number) => string) => string} - */ -function colorizeSign(n, format) { - if (n > 0) { - return kleur.red(kleur.bold("+")) + format(n); - } else if (n < 0) { - // Negate the value so that we don't get a double negative sign. - return kleur.green().bold("-") + format(-n); - } else { - return format(n); - } -} - -/** @type {(difference: Difference, format: (n: number) => string) => string} */ -function formatDifference({ absolute, relative }, format) { - let word, rel, abs; - if (absolute.low > 0 && relative.low > 0) { - word = kleur.bold().red("slower"); - rel = formatConfidenceInterval(relative, percent); - abs = formatConfidenceInterval(absolute, format); - } else if (absolute.high < 0 && relative.high < 0) { - word = kleur.bold().green("faster"); - rel = formatConfidenceInterval(negate(relative), percent); - abs = formatConfidenceInterval(negate(absolute), format); - } else { - word = kleur.bold().blue("unsure"); - rel = formatConfidenceInterval(relative, (n) => colorizeSign(n, percent)); - abs = formatConfidenceInterval(absolute, (n) => colorizeSign(n, format)); - } - - return `${word}\n${rel}\n${abs}`; -} - -/** @type {(n: number) => string} */ -function percent(n) { - return (n * 100).toFixed(0) + "%"; -} - -/** @type {(n: number) => string} */ -function milli(n) { - return n.toFixed(2) + "ms"; -} - -/** @type {(n: number) => string} */ -function megabytes(n) { - return n.toFixed(2) + "MB"; -} - -/** @type {(ci: ConfidenceInterval) => ConfidenceInterval} */ -function negate(ci) { - return { - low: -ci.high, - high: -ci.low, - }; -} diff --git a/cli/src/results.js b/cli/src/results.js index 7b4d3aa..62df048 100644 --- a/cli/src/results.js +++ b/cli/src/results.js @@ -1,6 +1,11 @@ -import { displayTable } from "./format.js"; +import kleur from "kleur"; +import table from "table"; import { computeStats } from "./stats.js"; -import { parseBenchmarkId } from "./utils.js"; +import { + makeBenchmarkLabel, + makeDepGroupLabel, + parseBenchmarkId, +} from "./utils.js"; /** @type {(results: TachResult[]) => Promise} */ export async function displayResults(tachResults) { @@ -63,3 +68,199 @@ function buildBenchmarkResults(tachResults) { return results; } + +/** @type {(benchmarkResult: BenchmarkResult, fixedDimensions: Dimension[]) => string} */ +function getTableTitle(benchmarkResult, fixedDimensions) { + const benchmarkName = benchmarkResult.benchName; + const browser = benchmarkResult.browser; + const measurementName = benchmarkResult.measurement.name ?? ""; + + const browserName = `${browser.name}${browser.headless ? "-headless" : ""}`; + const firstVariation = benchmarkResult.variations[0]; + const details = [ + browserName, + ...fixedDimensions.map((d) => d.format(firstVariation)), + ]; + + return `${kleur.bold(benchmarkName)} - ${kleur.bold(measurementName)} (${details.join("; ")})`; +} + +/** @type {Dimension} */ +const sampleSizeDimension = { + format: (r) => r.samples.length.toString(), + label: "Sample Size", +}; + +/** @type {Dimension} */ +const implementationDimension = { + format: (r) => r.implementation, + label: "Implementation", +}; + +/** @type {Dimension} */ +const depGroupDimension = { + format: (r) => makeDepGroupLabel(r.dependencies), + label: "Dependency Group", +}; + +/** @type {(format: (n: number) => string) => Dimension} */ +const createMeanValueDimension = (format) => ({ + format: (r) => { + return [ + formatConfidenceInterval(r.stats.meanCI, format), + "", + r.stats.histogram, + ].join("\n"); + }, + label: "Mean", + tableConfig: { alignment: "right" }, +}); + +/** @type {(benchmark: BenchmarkResult[]) => any} */ +function displayTable(benchmarkResults) { + for (let benchmarkResult of benchmarkResults) { + let formatNum = milli; + if (benchmarkResult.measurement.name === "usedJSHeapSize") { + formatNum = megabytes; + } + + const sampleSizeSet = new Set( + benchmarkResult.variations.map((r) => r.samples.length), + ); + const implSet = new Set( + benchmarkResult.variations.map((r) => r.implementation), + ); + const depGroupSet = new Set( + benchmarkResult.variations.map((r) => r.depGroupId), + ); + + const fixedDimensions = [ + ...(sampleSizeSet.size === 1 ? [sampleSizeDimension] : []), + ...(implSet.size === 1 ? [implementationDimension] : []), + ...(depGroupSet.size === 1 ? [depGroupDimension] : []), + ]; + + const varyingDimensions = [ + ...(implSet.size === 1 ? [] : [implementationDimension]), + ...(depGroupSet.size === 1 ? [] : [depGroupDimension]), + ...(sampleSizeSet.size === 1 ? [] : [sampleSizeDimension]), + ]; + + /** @type {Dimension[]} */ + const vsDimensions = []; + if (benchmarkResult.variations.length > 1) { + const includeDepGroup = depGroupSet.size > 1; + const includeImpl = implSet.size > 1; + + for (let i = 0; i < benchmarkResult.variations.length; i++) { + const variation = benchmarkResult.variations[i]; + + vsDimensions.push({ + label: + "vs " + + makeBenchmarkLabel( + includeImpl ? variation.implementation : null, + includeDepGroup ? variation.dependencies : null, + ), + format: (r) => { + const diff = r.differences[i]; + if (!diff) return kleur.gray("\n-"); + return formatDifference(diff, formatNum); + }, + tableConfig: { alignment: "right" }, + }); + } + } + + /** @type {Dimension[]} */ + const tableDimensions = [ + ...varyingDimensions, + createMeanValueDimension(formatNum), + ...vsDimensions, + ]; + + console.log(getTableTitle(benchmarkResult, fixedDimensions)); + console.log( + horizontalTerminalTable(tableDimensions, benchmarkResult.variations), + ); + } +} + +/** @type {(dimensions: Dimension[], results: VariationResult[]) => string} */ +function horizontalTerminalTable(dimensions, results) { + const rows = [ + dimensions.map((d) => kleur.bold(d.label)), + ...results.map((r) => dimensions.map((d) => d.format(r))), + ]; + + return table.table(rows, { + border: table.getBorderCharacters("norc"), + columns: dimensions.map((d) => d.tableConfig ?? {}), + }); +} + +/** + * Format a confidence interval as "[low, high]". + * @type {(ci: ConfidenceInterval, format: (n: number) => string) => string} + */ +function formatConfidenceInterval(ci, format) { + return `${format(ci.low)} ${kleur.gray("-")} ${format(ci.high)}`; +} + +/** + * Prefix positive numbers with a red "+" and negative ones with a green "-". + * @type {(n: number, format: (n: number) => string) => string} + */ +function colorizeSign(n, format) { + if (n > 0) { + return kleur.red(kleur.bold("+")) + format(n); + } else if (n < 0) { + // Negate the value so that we don't get a double negative sign. + return kleur.green().bold("-") + format(-n); + } else { + return format(n); + } +} + +/** @type {(difference: Difference, format: (n: number) => string) => string} */ +function formatDifference({ absolute, relative }, format) { + let word, rel, abs; + if (absolute.low > 0 && relative.low > 0) { + word = kleur.bold().red("slower"); + rel = formatConfidenceInterval(relative, percent); + abs = formatConfidenceInterval(absolute, format); + } else if (absolute.high < 0 && relative.high < 0) { + word = kleur.bold().green("faster"); + rel = formatConfidenceInterval(negate(relative), percent); + abs = formatConfidenceInterval(negate(absolute), format); + } else { + word = kleur.bold().blue("unsure"); + rel = formatConfidenceInterval(relative, (n) => colorizeSign(n, percent)); + abs = formatConfidenceInterval(absolute, (n) => colorizeSign(n, format)); + } + + return `${word}\n${rel}\n${abs}`; +} + +/** @type {(n: number) => string} */ +function percent(n) { + return (n * 100).toFixed(0) + "%"; +} + +/** @type {(n: number) => string} */ +function milli(n) { + return n.toFixed(2) + "ms"; +} + +/** @type {(n: number) => string} */ +function megabytes(n) { + return n.toFixed(2) + "MB"; +} + +/** @type {(ci: ConfidenceInterval) => ConfidenceInterval} */ +function negate(ci) { + return { + low: -ci.high, + high: -ci.low, + }; +}