Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/web/__bench__/bench.ts

Raw
/**
 * Benchmarks for web extension hot paths.
 *
 * Usage:
 *   bun run __bench__/bench.ts
 */

import { parseDdgResults } from "../ddg.js";
import { extractMainContent, sanitizeInjectionVectors } from "../extract.js";
import { cleanTableClasses, stripResidualTags } from "../pandoc.js";
import {
	makeChunks,
	makeDdgHtml,
	makeEntityHeavyText,
	makeHtmlWithInjection,
	makeLargeArticle,
	makePandocOutput,
	makeTableHtml,
} from "./fixtures.js";

// -- classifyContentType inlined (not exported from index.ts) ---------------

const CR: Array<{
	test: (ct: string) => boolean;
	result: { kind: string; officeFormat?: string };
}> = [
	{
		test: (ct) => ct.includes("text/html") || ct.includes("application/xhtml"),
		result: { kind: "html" },
	},
	{ test: (ct) => ct.includes("application/json"), result: { kind: "json" } },
	{
		test: (ct) =>
			ct.includes("application/xml") || ct.includes("application/yaml"),
		result: { kind: "text" },
	},
	{
		test: (ct) =>
			ct.startsWith("text/") || ct.includes("csv") || ct.includes("tsv"),
		result: { kind: "text" },
	},
	{ test: (ct) => ct.includes("application/pdf"), result: { kind: "pdf" } },
	{
		test: (ct) =>
			ct.includes("officedocument.wordprocessingml") || ct.includes("msword"),
		result: { kind: "office", officeFormat: "docx" },
	},
	{
		test: (ct) => ct.includes("opendocument.text"),
		result: { kind: "office", officeFormat: "odt" },
	},
	{
		test: (ct) => ct.includes("application/epub"),
		result: { kind: "office", officeFormat: "epub" },
	},
	{
		test: (ct) =>
			ct.includes("application/rtf") || ct.includes("text/richtext"),
		result: { kind: "office", officeFormat: "rtf" },
	},
	{ test: (ct) => ct.startsWith("image/"), result: { kind: "image" } },
	{
		test: (ct) =>
			ct.startsWith("video/") ||
			ct.startsWith("audio/") ||
			ct.includes("mpeg") ||
			ct.includes("mp4") ||
			ct.includes("webm") ||
			ct.includes("ogg"),
		result: { kind: "media" },
	},
];

function classifyContentType(ct: string) {
	const lower = ct.toLowerCase().split(";")[0].trim();
	return CR.find((r) => r.test(lower))?.result ?? { kind: "binary" };
}

// -- Micro-benchmark harness -----------------------------------------------

const MIN_DURATION_MS = 500;
const WARMUP = 5;

function bench(name: string, fn: () => void) {
	for (let i = 0; i < WARMUP; i++) fn();

	const t0 = performance.now();
	for (let i = 0; i < 10; i++) fn();
	const perIter = (performance.now() - t0) / 10;
	const iterations = Math.max(
		50,
		Math.min(10_000, Math.ceil(MIN_DURATION_MS / perIter)),
	);

	const times: number[] = [];
	for (let i = 0; i < iterations; i++) {
		const start = performance.now();
		fn();
		times.push(performance.now() - start);
	}

	times.sort((a, b) => a - b);
	const totalMs = times.reduce((s, t) => s + t, 0);

	return {
		name,
		iterations,
		totalMs,
		opsPerSec: Math.round((iterations / totalMs) * 1000),
		avgUs: Math.round((totalMs / iterations) * 1000),
		p50Us: Math.round(times[Math.floor(times.length * 0.5)] * 1000),
		p99Us: Math.round(times[Math.floor(times.length * 0.99)] * 1000),
	};
}

function fmt(r: ReturnType<typeof bench>): string {
	const ops = r.opsPerSec.toLocaleString().padStart(10);
	const avg = String(r.avgUs).padStart(8);
	const p50 = String(r.p50Us).padStart(8);
	const p99 = String(r.p99Us).padStart(8);
	const iters = String(r.iterations).padStart(6);
	return `${r.name.padEnd(52)} ${ops} ops/s  avg=${avg}us  p50=${p50}us  p99=${p99}us  [${iters}]`;
}

// -- Run benchmarks ---------------------------------------------------------

console.log("Web Extension Benchmarks");
console.log("=".repeat(120));

const ddgHtml = makeDdgHtml(15);
const injectHtml = makeHtmlWithInjection(40);
const largeArticle = makeLargeArticle(100);
const pandocOutput = makePandocOutput(40);
const tableHtml = makeTableHtml(10);
const entityText = makeEntityHeavyText(200);
const chunks100 = makeChunks(100, 10);
const chunks1000 = makeChunks(1000, 1);
const contentTypes = [
	"text/html; charset=utf-8",
	"application/json",
	"text/plain; charset=utf-8",
	"application/pdf",
	"image/png",
	"video/mp4",
	"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
	"application/octet-stream",
];

const results = [
	// DDG
	bench("parseDdgResults (15 results, ~20KB)", () => parseDdgResults(ddgHtml)),

	// Sanitization
	bench("sanitizeInjectionVectors (~40KB)", () =>
		sanitizeInjectionVectors(injectHtml),
	),
	bench("extractMainContent (article, ~40KB)", () =>
		extractMainContent(injectHtml),
	),
	bench("sanitizeInjectionVectors (~100KB)", () =>
		sanitizeInjectionVectors(largeArticle),
	),
	bench("extractMainContent (article, ~100KB)", () =>
		extractMainContent(largeArticle),
	),

	// Pandoc
	bench("stripResidualTags (~40KB)", () => stripResidualTags(pandocOutput)),
	bench("cleanTableClasses (10 tables)", () => cleanTableClasses(tableHtml)),

	// Content-Type
	bench("classifyContentType (8 types)", () => {
		for (const ct of contentTypes) classifyContentType(ct);
	}),
	bench("classifyContentType x8000", () => {
		for (let i = 0; i < 1000; i++) {
			for (const ct of contentTypes) classifyContentType(ct);
		}
	}),

	// Buffer
	bench("Buffer.concat (100x10KB=1MB)", () => Buffer.concat(chunks100)),
	bench("Buffer.concat (1000x1KB=1MB)", () => Buffer.concat(chunks1000)),

	// Entity decoding
	bench("decodeHTMLEntities (200 entities)", () => {
		entityText
			.replace(/&amp;/g, "&")
			.replace(/&lt;/g, "<")
			.replace(/&gt;/g, ">")
			.replace(/&quot;/g, '"')
			.replace(/&#39;/g, "'")
			.replace(/&#x27;/g, "'")
			.replace(/&apos;/g, "'")
			.replace(/&nbsp;/g, " ")
			.replace(/&#x2F;/g, "/")
			.replace(/&#(\d+);/g, (_, n) => String.fromCodePoint(parseInt(n, 10)))
			.replace(/&#x([0-9a-f]+);/gi, (_, n) =>
				String.fromCodePoint(parseInt(n, 16)),
			)
			.trim();
	}),
];

for (const r of results) console.log(fmt(r));
console.log();