import type { TaskResult } from "tinybench"; import { Bench } from "tinybench"; import type { SeedResult } from "./seed"; import { logInfo, logStep, logSuccess } from "./log"; import { formatMs, formatNumber } from "./utils"; // Type guard for completed task results type CompletedTaskResult = Extract; export interface BenchmarkRow { name: string; ops: number; mean: number; p75: number; p99: number; samples: number; } export interface BenchmarkOptions { timeMs?: number; warmupMs?: number; } export async function runBenchmarks( seed: SeedResult, options?: BenchmarkOptions, ): Promise { const bench = new Bench({ time: options?.timeMs ?? 1000, warmupTime: options?.warmupMs ?? 300, }); const sampleTag = seed.tags[0]; const sampleList = seed.lists[0]; const sampleIds = seed.bookmarks.slice(0, 50).map((b) => b.id); bench.add("bookmarks.getBookmarks (page)", async () => { await seed.trpc.bookmarks.getBookmarks.query({ limit: 50, }); }); if (sampleTag) { bench.add("bookmarks.getBookmarks (tag filter)", async () => { await seed.trpc.bookmarks.getBookmarks.query({ limit: 50, tagId: sampleTag.id, }); }); } if (sampleList) { bench.add("bookmarks.getBookmarks (list filter)", async () => { await seed.trpc.bookmarks.getBookmarks.query({ limit: 50, listId: sampleList.id, }); }); } if (sampleList && sampleIds.length > 0) { bench.add("lists.getListsOfBookmark", async () => { await seed.trpc.lists.getListsOfBookmark.query({ bookmarkId: sampleIds[0], }); }); } bench.add("bookmarks.searchBookmarks", async () => { await seed.trpc.bookmarks.searchBookmarks.query({ text: seed.searchTerm, limit: 20, }); }); bench.add("bookmarks.getBookmarks (by ids)", async () => { await seed.trpc.bookmarks.getBookmarks.query({ ids: sampleIds.slice(0, 20), includeContent: false, }); }); logStep("Running benchmarks"); await bench.run(); logSuccess("Benchmarks complete"); const rows = bench.tasks .map((task) => { const result = task.result; // Check for errored state if ("error" in result) { console.error(`\n⚠️ Benchmark "${task.name}" failed with error:`); console.error(result.error); return null; } // Check if task completed successfully if (result.state !== "completed") { console.warn( `\n⚠️ Benchmark "${task.name}" did not complete. State: ${result.state}`, ); return null; } return toRow(task.name, result); }) .filter(Boolean) as BenchmarkRow[]; renderTable(rows); logInfo( "ops/s uses tinybench's hz metric; durations are recorded in milliseconds.", ); return rows; } function toRow(name: string, result: CompletedTaskResult): BenchmarkRow { // The statistics are now in result.latency and result.throughput const latency = result.latency; const throughput = result.throughput; return { name, ops: throughput.mean, // ops/s is the mean throughput mean: latency.mean, p75: latency.p75, p99: latency.p99, samples: latency.samplesCount, }; } function renderTable(rows: BenchmarkRow[]): void { const headers = ["Benchmark", "ops/s", "avg", "p75", "p99", "samples"]; const data = rows.map((row) => [ row.name, formatNumber(row.ops, 1), formatMs(row.mean), formatMs(row.p75), formatMs(row.p99), String(row.samples), ]); const columnWidths = headers.map((header, index) => Math.max(header.length, ...data.map((row) => row[index].length)), ); const formatRow = (cells: string[]): string => cells.map((cell, index) => cell.padEnd(columnWidths[index])).join(" "); console.log(""); console.log(formatRow(headers)); console.log(columnWidths.map((width) => "-".repeat(width)).join(" ")); data.forEach((row) => console.log(formatRow(row))); console.log(""); }