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(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, '"')
.replace(/'/g, "'")
.replace(/'/g, "'")
.replace(/'/g, "'")
.replace(/ /g, " ")
.replace(///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();