Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
476
scripts/bench-web-fetch.ts
Normal file
476
scripts/bench-web-fetch.ts
Normal file
@@ -0,0 +1,476 @@
|
||||
// Web fetch benchmark covers direct response loading, HTML extraction, and fallback cleanup.
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { performance } from "node:perf_hooks";
|
||||
import { extractBasicHtmlContent } from "../src/agents/tools/web-fetch-utils.js";
|
||||
import { createWebFetchTool } from "../src/agents/tools/web-fetch.js";
|
||||
import type { OpenClawConfig } from "../src/config/types.openclaw.js";
|
||||
import type { LookupFn } from "../src/infra/net/ssrf.js";
|
||||
import { extractReadableContent } from "../src/web-fetch/content-extractors.runtime.js";
|
||||
import { stripLeadingPackageManagerSeparator } from "./lib/arg-utils.mjs";
|
||||
|
||||
type BenchmarkCaseId =
|
||||
| "tool-create"
|
||||
| "tool-text"
|
||||
| "tool-markdown"
|
||||
| "tool-html-article"
|
||||
| "tool-html-article-text"
|
||||
| "tool-html-shell"
|
||||
| "extract-readable-article"
|
||||
| "extract-readable-article-text"
|
||||
| "extract-basic-shell";
|
||||
|
||||
type BenchmarkCase = {
|
||||
id: BenchmarkCaseId;
|
||||
label: string;
|
||||
run: () => Promise<void> | void;
|
||||
};
|
||||
|
||||
type Options = {
|
||||
cases: BenchmarkCaseId[];
|
||||
json: boolean;
|
||||
output?: string;
|
||||
runs: number;
|
||||
warmup: number;
|
||||
};
|
||||
|
||||
type SummaryStats = {
|
||||
avg: number;
|
||||
max: number;
|
||||
min: number;
|
||||
p50: number;
|
||||
p95: number;
|
||||
};
|
||||
|
||||
type CaseReport = {
|
||||
id: BenchmarkCaseId;
|
||||
label: string;
|
||||
samplesMs: number[];
|
||||
summaryMs: SummaryStats;
|
||||
};
|
||||
|
||||
type BenchmarkReport = {
|
||||
cases: CaseReport[];
|
||||
node: string;
|
||||
options: Omit<Options, "json">;
|
||||
rssMb: number;
|
||||
};
|
||||
|
||||
const ALL_CASE_IDS = [
|
||||
"tool-create",
|
||||
"tool-text",
|
||||
"tool-markdown",
|
||||
"tool-html-article",
|
||||
"tool-html-article-text",
|
||||
"tool-html-shell",
|
||||
"extract-readable-article",
|
||||
"extract-readable-article-text",
|
||||
"extract-basic-shell",
|
||||
] as const satisfies readonly BenchmarkCaseId[];
|
||||
|
||||
const BOOLEAN_FLAGS = new Set(["--help", "-h", "--json"]);
|
||||
const VALUE_FLAGS = new Set(["--case", "--output", "--runs", "--warmup"]);
|
||||
|
||||
class CliArgumentError extends Error {
|
||||
override name = "CliArgumentError";
|
||||
}
|
||||
|
||||
const ARTICLE_HTML = `<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Web Fetch Benchmark Article</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav>${Array.from({ length: 24 }, (_, index) => `<a href="/nav-${index}">Nav ${index}</a>`).join("")}</nav>
|
||||
<main>
|
||||
<article>
|
||||
<h1>Web Fetch Benchmark Article</h1>
|
||||
${Array.from(
|
||||
{ length: 180 },
|
||||
(_, index) =>
|
||||
`<p>Paragraph ${index} carries readable benchmark content with enough prose for Readability to score it as article body text.</p>`,
|
||||
).join("\n")}
|
||||
</article>
|
||||
</main>
|
||||
<footer>Repeated footer chrome that should not dominate extracted content.</footer>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
const SHELL_HTML = `<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Shell App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script>window.__APP__ = true;</script>
|
||||
<noscript>Enable JavaScript to load this page.</noscript>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
const TEXT_BODY = "OpenClaw web_fetch direct text benchmark body.".repeat(160);
|
||||
const MARKDOWN_BODY = "# Web Fetch Benchmark\n\n" + "- markdown list item\n".repeat(220);
|
||||
const OFFLINE_PROVIDER_ENV_VARS = ["FIRECRAWL_API_KEY"] as const;
|
||||
|
||||
const lookupFn: LookupFn = async () => [{ address: "93.184.216.34", family: 4 }];
|
||||
const toolConfig: OpenClawConfig = {
|
||||
tools: {
|
||||
web: {
|
||||
fetch: {
|
||||
cacheTtlMinutes: 0,
|
||||
firecrawl: { enabled: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function validateArgs(args: string[]): void {
|
||||
const seenSingularValueFlags = new Set<string>();
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const arg = args[index] ?? "";
|
||||
if (BOOLEAN_FLAGS.has(arg)) {
|
||||
continue;
|
||||
}
|
||||
if (VALUE_FLAGS.has(arg)) {
|
||||
if (arg !== "--case") {
|
||||
if (seenSingularValueFlags.has(arg)) {
|
||||
throw new CliArgumentError(`${arg} was provided more than once`);
|
||||
}
|
||||
seenSingularValueFlags.add(arg);
|
||||
}
|
||||
const value = args[index + 1];
|
||||
if (!value || value.startsWith("-")) {
|
||||
throw new CliArgumentError(`${arg} requires a value`);
|
||||
}
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
throw new CliArgumentError(`Unknown argument: ${arg}`);
|
||||
}
|
||||
}
|
||||
|
||||
function parsePositiveInteger(flag: string, fallback: number, args: string[]): number {
|
||||
const index = args.indexOf(flag);
|
||||
if (index === -1) {
|
||||
return fallback;
|
||||
}
|
||||
const raw = args[index + 1];
|
||||
const value = Number(raw);
|
||||
if (!Number.isInteger(value) || value <= 0) {
|
||||
throw new CliArgumentError(`${flag} must be a positive integer`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function parseNonNegativeInteger(flag: string, fallback: number, args: string[]): number {
|
||||
const index = args.indexOf(flag);
|
||||
if (index === -1) {
|
||||
return fallback;
|
||||
}
|
||||
const raw = args[index + 1];
|
||||
const value = Number(raw);
|
||||
if (!Number.isInteger(value) || value < 0) {
|
||||
throw new CliArgumentError(`${flag} must be a non-negative integer`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function parseCases(args: string[]): BenchmarkCaseId[] {
|
||||
const requested: string[] = [];
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
if (args[index] === "--case") {
|
||||
requested.push(args[index + 1] ?? "");
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
if (requested.length === 0 || requested.includes("all")) {
|
||||
return [...ALL_CASE_IDS];
|
||||
}
|
||||
const valid = new Set<string>(ALL_CASE_IDS);
|
||||
for (const id of requested) {
|
||||
if (!valid.has(id)) {
|
||||
throw new CliArgumentError(
|
||||
`--case must be one of all, ${ALL_CASE_IDS.join(", ")}; got ${JSON.stringify(id)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
return requested as BenchmarkCaseId[];
|
||||
}
|
||||
|
||||
function parseFlagValue(flag: string, args: string[]): string | undefined {
|
||||
const index = args.indexOf(flag);
|
||||
return index === -1 ? undefined : args[index + 1];
|
||||
}
|
||||
|
||||
function parseOptions(args = process.argv.slice(2)): Options {
|
||||
const normalizedArgs = stripLeadingPackageManagerSeparator(args);
|
||||
validateArgs(normalizedArgs);
|
||||
return {
|
||||
cases: parseCases(normalizedArgs),
|
||||
json: normalizedArgs.includes("--json"),
|
||||
output: parseFlagValue("--output", normalizedArgs),
|
||||
runs: parsePositiveInteger("--runs", 20, normalizedArgs),
|
||||
warmup: parseNonNegativeInteger("--warmup", 3, normalizedArgs),
|
||||
};
|
||||
}
|
||||
|
||||
function printUsage(): void {
|
||||
process.stdout.write(`OpenClaw web_fetch benchmark
|
||||
|
||||
Usage:
|
||||
pnpm perf:web-fetch -- [options]
|
||||
node --import tsx scripts/bench-web-fetch.ts [options]
|
||||
|
||||
Options:
|
||||
--case <id> Case to run; repeatable. Use "all" for every case.
|
||||
--runs <n> Measured runs per case (default: 20)
|
||||
--warmup <n> Warmup runs per case (default: 3)
|
||||
--output <path> Write JSON report
|
||||
--json Print JSON report
|
||||
--help, -h Show this text
|
||||
|
||||
Cases:
|
||||
${ALL_CASE_IDS.join("\n ")}
|
||||
`);
|
||||
}
|
||||
|
||||
function round(value: number): number {
|
||||
return Math.round(value * 1000) / 1000;
|
||||
}
|
||||
|
||||
function percentile(values: number[], p: number): number {
|
||||
if (values.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
const sorted = values.toSorted((left, right) => left - right);
|
||||
const index = Math.min(sorted.length - 1, Math.floor((sorted.length - 1) * p));
|
||||
return round(sorted[index] ?? 0);
|
||||
}
|
||||
|
||||
function stats(values: number[]): SummaryStats {
|
||||
if (values.length === 0) {
|
||||
return { avg: 0, max: 0, min: 0, p50: 0, p95: 0 };
|
||||
}
|
||||
const total = values.reduce((sum, value) => sum + value, 0);
|
||||
return {
|
||||
avg: round(total / values.length),
|
||||
max: round(Math.max(...values)),
|
||||
min: round(Math.min(...values)),
|
||||
p50: percentile(values, 0.5),
|
||||
p95: percentile(values, 0.95),
|
||||
};
|
||||
}
|
||||
|
||||
function installMockFetch(params: { body: string; contentType: string }) {
|
||||
const fetchImpl = (async () =>
|
||||
new Response(params.body, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"content-type": params.contentType,
|
||||
},
|
||||
})) as typeof globalThis.fetch & { mock: object };
|
||||
// fetchWithSsrFGuard preserves dispatcher support unless global fetch is a
|
||||
// test double. The marker keeps this benchmark offline and deterministic.
|
||||
fetchImpl.mock = {};
|
||||
globalThis.fetch = fetchImpl;
|
||||
}
|
||||
|
||||
async function withOfflineProviderEnv<T>(run: () => Promise<T>): Promise<T> {
|
||||
const previous = new Map<string, string | undefined>();
|
||||
for (const name of OFFLINE_PROVIDER_ENV_VARS) {
|
||||
previous.set(name, process.env[name]);
|
||||
process.env[name] = "";
|
||||
}
|
||||
try {
|
||||
return await run();
|
||||
} finally {
|
||||
for (const [name, value] of previous) {
|
||||
if (value === undefined) {
|
||||
delete process.env[name];
|
||||
} else {
|
||||
process.env[name] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createTool() {
|
||||
const tool = createWebFetchTool({
|
||||
config: toolConfig,
|
||||
lookupFn,
|
||||
sandboxed: false,
|
||||
});
|
||||
if (!tool?.execute) {
|
||||
throw new Error("web_fetch tool was not created");
|
||||
}
|
||||
return tool;
|
||||
}
|
||||
|
||||
function createCases(): Record<BenchmarkCaseId, BenchmarkCase> {
|
||||
const textTool = createTool();
|
||||
const markdownTool = createTool();
|
||||
const articleTool = createTool();
|
||||
const articleTextTool = createTool();
|
||||
const shellTool = createTool();
|
||||
return {
|
||||
"tool-create": {
|
||||
id: "tool-create",
|
||||
label: "create web_fetch tool",
|
||||
run: () => {
|
||||
createTool();
|
||||
},
|
||||
},
|
||||
"tool-text": {
|
||||
id: "tool-text",
|
||||
label: "execute text/plain fetch",
|
||||
run: async () => {
|
||||
installMockFetch({ body: TEXT_BODY, contentType: "text/plain; charset=utf-8" });
|
||||
await textTool.execute("bench", { url: "https://example.com/plain" });
|
||||
},
|
||||
},
|
||||
"tool-markdown": {
|
||||
id: "tool-markdown",
|
||||
label: "execute text/markdown fetch",
|
||||
run: async () => {
|
||||
installMockFetch({ body: MARKDOWN_BODY, contentType: "text/markdown; charset=utf-8" });
|
||||
await markdownTool.execute("bench", { url: "https://example.com/markdown" });
|
||||
},
|
||||
},
|
||||
"tool-html-article": {
|
||||
id: "tool-html-article",
|
||||
label: "execute article HTML fetch",
|
||||
run: async () => {
|
||||
installMockFetch({ body: ARTICLE_HTML, contentType: "text/html; charset=utf-8" });
|
||||
await articleTool.execute("bench", { url: "https://example.com/article" });
|
||||
},
|
||||
},
|
||||
"tool-html-article-text": {
|
||||
id: "tool-html-article-text",
|
||||
label: "execute article HTML fetch as text",
|
||||
run: async () => {
|
||||
installMockFetch({ body: ARTICLE_HTML, contentType: "text/html; charset=utf-8" });
|
||||
await articleTextTool.execute("bench", {
|
||||
url: "https://example.com/article-text",
|
||||
extractMode: "text",
|
||||
});
|
||||
},
|
||||
},
|
||||
"tool-html-shell": {
|
||||
id: "tool-html-shell",
|
||||
label: "execute shell HTML fallback fetch",
|
||||
run: async () => {
|
||||
installMockFetch({ body: SHELL_HTML, contentType: "text/html; charset=utf-8" });
|
||||
await shellTool.execute("bench", { url: "https://example.com/shell" });
|
||||
},
|
||||
},
|
||||
"extract-readable-article": {
|
||||
id: "extract-readable-article",
|
||||
label: "extract readable article HTML",
|
||||
run: async () => {
|
||||
await extractReadableContent({
|
||||
html: ARTICLE_HTML,
|
||||
url: "https://example.com/article",
|
||||
extractMode: "markdown",
|
||||
config: toolConfig,
|
||||
});
|
||||
},
|
||||
},
|
||||
"extract-readable-article-text": {
|
||||
id: "extract-readable-article-text",
|
||||
label: "extract readable article HTML as text",
|
||||
run: async () => {
|
||||
await extractReadableContent({
|
||||
html: ARTICLE_HTML,
|
||||
url: "https://example.com/article-text",
|
||||
extractMode: "text",
|
||||
config: toolConfig,
|
||||
});
|
||||
},
|
||||
},
|
||||
"extract-basic-shell": {
|
||||
id: "extract-basic-shell",
|
||||
label: "extract basic shell HTML",
|
||||
run: async () => {
|
||||
await extractBasicHtmlContent({
|
||||
html: SHELL_HTML,
|
||||
extractMode: "markdown",
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function measureCase(testCase: BenchmarkCase, options: Options): Promise<CaseReport> {
|
||||
for (let index = 0; index < options.warmup; index += 1) {
|
||||
await testCase.run();
|
||||
}
|
||||
const samplesMs: number[] = [];
|
||||
for (let index = 0; index < options.runs; index += 1) {
|
||||
const started = performance.now();
|
||||
await testCase.run();
|
||||
samplesMs.push(round(performance.now() - started));
|
||||
}
|
||||
return {
|
||||
id: testCase.id,
|
||||
label: testCase.label,
|
||||
samplesMs,
|
||||
summaryMs: stats(samplesMs),
|
||||
};
|
||||
}
|
||||
|
||||
function printProofLines(report: BenchmarkReport): void {
|
||||
for (const testCase of report.cases) {
|
||||
const summary = testCase.summaryMs;
|
||||
console.log(
|
||||
`WEB_FETCH_BENCH_CASE=${testCase.id} avg_ms=${summary.avg.toFixed(3)} p50_ms=${summary.p50.toFixed(3)} p95_ms=${summary.p95.toFixed(3)} max_ms=${summary.max.toFixed(3)}`,
|
||||
);
|
||||
}
|
||||
console.log(`WEB_FETCH_BENCH_RSS_MB=${report.rssMb.toFixed(1)}`);
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const args = process.argv.slice(2);
|
||||
if (args.includes("--help") || args.includes("-h")) {
|
||||
printUsage();
|
||||
return;
|
||||
}
|
||||
const options = parseOptions(args);
|
||||
const report = await withOfflineProviderEnv(async () => {
|
||||
const casesById = createCases();
|
||||
const cases: CaseReport[] = [];
|
||||
for (const caseId of options.cases) {
|
||||
cases.push(await measureCase(casesById[caseId], options));
|
||||
}
|
||||
return {
|
||||
cases,
|
||||
node: process.version,
|
||||
options: {
|
||||
cases: options.cases,
|
||||
output: options.output,
|
||||
runs: options.runs,
|
||||
warmup: options.warmup,
|
||||
},
|
||||
rssMb: Math.round((process.memoryUsage().rss / 1024 / 1024) * 10) / 10,
|
||||
};
|
||||
});
|
||||
if (options.output) {
|
||||
await mkdir(path.dirname(options.output), { recursive: true });
|
||||
await writeFile(options.output, `${JSON.stringify(report, null, 2)}\n`);
|
||||
}
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify(report, null, 2));
|
||||
} else {
|
||||
printProofLines(report);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error: unknown) => {
|
||||
if (error instanceof CliArgumentError) {
|
||||
console.error(error.message);
|
||||
process.exitCode = 2;
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
Reference in New Issue
Block a user