pages: deploy the WASM benchmark site

Built by scripts/build-wasm-site.sh; the loader fetches the gzipped
module and decompresses client-side, and coi-serviceworker.js supplies
COOP/COEP so SharedArrayBuffer works without server headers.
This commit is contained in:
Mihaela Mihaljevic 2026-07-05 06:07:19 +02:00
commit 01b38309e8
24 changed files with 1808 additions and 0 deletions

View File

@ -0,0 +1,24 @@
# Managed by rules-swift (scripts/sync-rules.sh). Verifies this repo tracks the
# CURRENT shared-rules baseline. The repo's own .swiftformat/.swiftlint.yml are
# repo-specific (baseline + project rules) and are intentionally NOT byte-checked.
name: rules-conformance
on:
push:
pull_request:
workflow_dispatch:
jobs:
rules:
runs-on: macos
steps:
- uses: actions/checkout@v4
- name: Rules baseline freshness (VERSION)
run: |
BASE=https://codeberg.org/Mihaela/rules-swift/raw/branch/main
canon=$(curl -fsSL "$BASE/VERSION" | tr -d '[:space:]')
mine=$(tr -d '[:space:]' < .rules-version 2>/dev/null || echo MISSING)
if [ "$mine" != "$canon" ]; then
echo "STALE: shared-rules baseline is v$canon, this repo tracks v$mine."
echo "Review the baseline diff, fold it into this repo's rules, then set .rules-version to $canon."
exit 1
fi
echo "rules baseline FRESH at v$canon (repo-specific rules preserved, not checked)"

16
.github/workflows/linux.yml vendored Normal file
View File

@ -0,0 +1,16 @@
name: Linux
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
container: swift:6.2
steps:
- uses: actions/checkout@v6
- name: Build and test
run: bash scripts/check.sh

15
.github/workflows/macos.yml vendored Normal file
View File

@ -0,0 +1,15 @@
name: macOS
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
jobs:
test:
runs-on: macos-latest
steps:
- uses: actions/checkout@v6
- name: Build and test
run: bash scripts/check.sh

357
.github/workflows/wasm-site.yml vendored Normal file
View File

@ -0,0 +1,357 @@
name: WASM Test Site
on:
push:
branches: [main]
paths:
- "Package.swift"
- "Package.resolved"
- "Sources/PureYAMLStreaming/**"
- "Sources/PureYAMLStreamingWasmSmoke/**"
- "WasmSite/**"
- "scripts/build-wasm-site.sh"
- ".github/workflows/wasm-site.yml"
pull_request:
paths:
- "Package.swift"
- "Package.resolved"
- "Sources/PureYAMLStreaming/**"
- "Sources/PureYAMLStreamingWasmSmoke/**"
- "WasmSite/**"
- "scripts/build-wasm-site.sh"
- ".github/workflows/wasm-site.yml"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: pureyaml-streaming-wasm-site-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install build tools
run: |
sudo apt-get update
sudo apt-get install -y binaryen gzip libcurl4-openssl-dev
- name: Install swiftly
run: |
curl -L https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz > swiftly.tar.gz
tar zxf swiftly.tar.gz
./swiftly init --quiet-shell-followup --assume-yes --skip-install
- name: Install Swift 6.3.2 and Wasm SDK
run: |
. "${SWIFTLY_HOME_DIR:-$HOME/.local/share/swiftly}/env.sh"
swiftly install --use 6.3.2
swiftly run swift sdk install https://download.swift.org/swift-6.3.2-release/wasm-sdk/swift-6.3.2-RELEASE/swift-6.3.2-RELEASE_wasm.artifactbundle.tar.gz --checksum a61f0584c93283589f8b2f42db05c1f9a182b506c2957271402992655591dd7c +6.3.2
- name: Build WASM site
run: |
. "${SWIFTLY_HOME_DIR:-$HOME/.local/share/swiftly}/env.sh"
bash scripts/build-wasm-site.sh
mkdir -p site
cp WasmSite/index.html site/index.html
cp WasmSite/main.js site/main.js
cp WasmSite/coi-serviceworker.js site/coi-serviceworker.js
cp WasmSite/stream-worker.js site/stream-worker.js
cp WasmSite/style.css site/style.css
cp WasmSite/pureyaml-streaming-wasm-smoke.wasm.gz site/pureyaml-streaming-wasm-smoke.wasm.gz
cp -R WasmSite/vendor site/vendor
mkdir -p site/fixtures
python3 - <<'PY'
from pathlib import Path
fixture = Path("site/fixtures/generated-large.yaml")
payload = "x" * 1024
with fixture.open("w", encoding="utf-8") as handle:
for index in range(3000):
handle.write("---\n")
handle.write(f"name: ci-doc-{index}\n")
handle.write(f"enabled: {'true' if index % 2 == 0 else 'false'}\n")
handle.write(f"payload: {payload}\n")
PY
ls -lh site
- name: Install Playwright Chromium
run: |
mkdir -p .build/playwright-ci
cd .build/playwright-ci
npm init -y
npm install playwright-chromium
npx playwright install --with-deps chromium
- name: Test site locally
run: |
python3 -m http.server 8766 --bind 127.0.0.1 --directory site &
server_pid=$!
trap 'kill "$server_pid"' EXIT
node <<'NODE'
const { chromium } = require('./.build/playwright-ci/node_modules/playwright-chromium');
async function gotoIsolated(page, url) {
await page.goto(url);
for (let attempt = 0; attempt < 6; attempt += 1) {
if (await page.evaluate(() => globalThis.crossOriginIsolated === true)) {
return;
}
await page.waitForTimeout(500);
await page.reload();
}
throw new Error('Expected page to become crossOriginIsolated');
}
function parsedStdout(output) {
const stdoutMatch = output.match(/stdout:\n([\s\S]*?)\n\nstderr:/);
return JSON.parse(stdoutMatch?.[1] || '{}');
}
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await gotoIsolated(page, 'http://127.0.0.1:8766/?v=' + Date.now());
const optionCount = await page.locator('#sampleSelect option').count();
if (optionCount < 10) {
throw new Error(`Expected at least 10 public YAML samples, got ${optionCount}`);
}
if (await page.locator('#streamButton').isDisabled()) {
throw new Error('Expected true streaming button to be enabled');
}
await page.fill('#urlInput', 'http://127.0.0.1:8766/fixtures/generated-large.yaml');
await page.click('#runButton');
await page.waitForFunction(
() => document.querySelector('#status')?.textContent === 'Passed',
null,
{ timeout: 60000 }
);
const output = await page.locator('#output').textContent();
const bytes = await page.locator('#bytesMetric').textContent();
const parse = await page.locator('#parseMetric').textContent();
const throughput = await page.locator('#throughputMetric').textContent();
const documents = await page.locator('#documentsMetric').textContent();
const runLog = await page.locator('#runLog').textContent();
const streamDocument = await page.locator('#streamDocument').textContent();
const parsed = parsedStdout(output);
if (parsed.ok !== true) throw new Error(output);
if (parsed.transport !== 'SwiftWASIHTTPClient.HostHTTPClient') throw new Error(output);
if (parsed.httpStatusCode !== 200) throw new Error(output);
if (parsed.documentCount !== 3000) throw new Error(output);
if (parsed.firstName !== 'ci-doc-0') throw new Error(output);
if (parsed.lastName !== 'ci-doc-2999') throw new Error(output);
if (!Number.isFinite(parsed.parseMilliseconds) || parsed.parseMilliseconds <= 0) {
throw new Error(`Expected positive parseMilliseconds, got ${parsed.parseMilliseconds}`);
}
if (!Number.isFinite(parsed.chunkCount) || parsed.chunkCount <= 1) {
throw new Error(`Expected multiple Swift chunks, got ${parsed.chunkCount}`);
}
if (parsed.chunkSize !== 31) {
throw new Error(`Expected chunkSize 31, got ${parsed.chunkSize}`);
}
if (parsed.maxChunkByteCount > parsed.chunkSize) {
throw new Error(`Expected max chunk <= chunkSize, got ${parsed.maxChunkByteCount}`);
}
if (parsed.scannerDocumentSourceCount !== 3000) {
throw new Error(`Expected scanner to emit 3000 sources, got ${parsed.scannerDocumentSourceCount}`);
}
if (documents !== '3000') {
throw new Error(`Expected document metric 3000, got: ${documents}`);
}
if (bytes === '-' || parse === '-') {
throw new Error(`Expected benchmark metrics, got bytes=${bytes}, parse=${parse}`);
}
if (!throughput.includes('/s')) {
throw new Error(`Expected throughput metric, got: ${throughput}`);
}
if (!runLog.includes('Fetching YAML bytes') || !runLog.includes('SwiftWASIHTTPClient.HostHTTPClient') || !runLog.includes('Starting Swift parser.') || !runLog.includes('ChunkedUTF8Reader consumed') || !runLog.includes('Benchmark completed successfully.')) {
throw new Error(`Expected detailed run log, got: ${runLog}`);
}
if (!streamDocument.includes('name: ci-doc-0') || !streamDocument.includes('name: ci-doc-2999')) {
throw new Error('Expected buffered document panel to show generated YAML');
}
if (await page.locator('#streamDocument .yaml-key').count() < 3) {
throw new Error('Expected buffered document panel syntax highlighting');
}
await page.click('#copyRunLogButton');
await page.waitForFunction(
() => document.querySelector('#copyRunLogButton')?.textContent === 'Copied',
null,
{ timeout: 5000 }
);
await page.click('#streamButton');
await page.waitForFunction(
() => document.querySelector('#status')?.textContent === 'Passed',
null,
{ timeout: 60000 }
);
const streamingOutput = await page.locator('#output').textContent();
const streamingRunLog = await page.locator('#runLog').textContent();
const streamingDocument = await page.locator('#streamDocument').textContent();
const streamingParsed = parsedStdout(streamingOutput);
if (streamingParsed.ok !== true) throw new Error(streamingOutput);
if (streamingParsed.transport !== 'SharedArrayBuffer fd_read stdin') throw new Error(streamingOutput);
if (streamingParsed.httpStatusCode !== 200) throw new Error(streamingOutput);
if (streamingParsed.documentCount !== 3000) throw new Error(streamingOutput);
if (streamingParsed.firstName !== 'ci-doc-0') throw new Error(streamingOutput);
if (streamingParsed.lastName !== 'ci-doc-2999') throw new Error(streamingOutput);
if (!Number.isFinite(streamingParsed.stdinReadCount) || streamingParsed.stdinReadCount <= 1) {
throw new Error(`Expected multiple stdin reads, got ${streamingParsed.stdinReadCount}`);
}
if (streamingParsed.maxStdinReadByteCount > streamingParsed.chunkSize) {
throw new Error(`Expected max stdin read <= chunkSize, got ${streamingParsed.maxStdinReadByteCount}`);
}
if (!streamingRunLog.includes('Streaming HTTP 200 response body into SharedArrayBuffer') || !streamingRunLog.includes('Network chunk') || !streamingRunLog.includes('Worker fd_read calls') || !streamingRunLog.includes('SharedArrayBuffer fd_read stdin')) {
throw new Error(`Expected true streaming run log, got: ${streamingRunLog}`);
}
if (!streamingDocument.includes('name: ci-doc-0') || !streamingDocument.includes('name: ci-doc-2999')) {
throw new Error('Expected streaming document panel to append generated YAML');
}
if (await page.locator('#streamDocument .yaml-key').count() < 3) {
throw new Error('Expected streaming document panel syntax highlighting');
}
await browser.close();
})();
NODE
- name: Configure Pages
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: actions/configure-pages@v5
- name: Upload Pages artifact
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: actions/upload-pages-artifact@v3
with:
path: site
deploy:
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
- name: Install Playwright Chromium
run: |
mkdir -p .build/playwright-live
cd .build/playwright-live
npm init -y
npm install playwright-chromium
npx playwright install --with-deps chromium
- name: Validate live Pages benchmark
env:
PAGE_URL: ${{ steps.deployment.outputs.page_url }}
run: |
node <<'NODE'
const { chromium } = require('./.build/playwright-live/node_modules/playwright-chromium');
async function gotoIsolated(page, url) {
await page.goto(url);
for (let attempt = 0; attempt < 6; attempt += 1) {
if (await page.evaluate(() => globalThis.crossOriginIsolated === true)) {
return;
}
await page.waitForTimeout(500);
await page.reload();
}
throw new Error('Expected live page to become crossOriginIsolated');
}
function parsedStdout(output) {
const stdoutMatch = output.match(/stdout:\n([\s\S]*?)\n\nstderr:/);
return JSON.parse(stdoutMatch?.[1] || '{}');
}
(async () => {
const pageURL = process.env.PAGE_URL;
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await gotoIsolated(page, pageURL + '?v=' + Date.now());
await page.fill('#urlInput', new URL('fixtures/generated-large.yaml', pageURL).href);
await page.click('#runButton');
await page.waitForFunction(
() => document.querySelector('#status')?.textContent === 'Passed',
null,
{ timeout: 60000 }
);
const output = await page.locator('#output').textContent();
const throughput = await page.locator('#throughputMetric').textContent();
const runLog = await page.locator('#runLog').textContent();
const streamDocument = await page.locator('#streamDocument').textContent();
const parsed = parsedStdout(output);
if (parsed.transport !== 'SwiftWASIHTTPClient.HostHTTPClient') throw new Error(output);
if (parsed.httpStatusCode !== 200) throw new Error(output);
if (parsed.documentCount !== 3000) throw new Error(output);
if (parsed.firstName !== 'ci-doc-0') throw new Error(output);
if (parsed.lastName !== 'ci-doc-2999') throw new Error(output);
if (!Number.isFinite(parsed.parseMilliseconds) || parsed.parseMilliseconds <= 0) {
throw new Error(`Expected positive parseMilliseconds, got ${parsed.parseMilliseconds}`);
}
if (!Number.isFinite(parsed.chunkCount) || parsed.chunkCount <= 1) {
throw new Error(`Expected multiple Swift chunks, got ${parsed.chunkCount}`);
}
if (parsed.chunkSize !== 31) {
throw new Error(`Expected chunkSize 31, got ${parsed.chunkSize}`);
}
if (parsed.maxChunkByteCount > parsed.chunkSize) {
throw new Error(`Expected max chunk <= chunkSize, got ${parsed.maxChunkByteCount}`);
}
if (parsed.scannerDocumentSourceCount !== 3000) {
throw new Error(`Expected scanner to emit 3000 sources, got ${parsed.scannerDocumentSourceCount}`);
}
if (!throughput.includes('/s')) {
throw new Error(`Expected live throughput metric, got: ${throughput}`);
}
if (!runLog.includes('Fetching YAML bytes') || !runLog.includes('SwiftWASIHTTPClient.HostHTTPClient') || !runLog.includes('Starting Swift parser.') || !runLog.includes('ChunkedUTF8Reader consumed') || !runLog.includes('Benchmark completed successfully.')) {
throw new Error(`Expected live detailed run log, got: ${runLog}`);
}
if (!streamDocument.includes('name: ci-doc-0') || !streamDocument.includes('name: ci-doc-2999')) {
throw new Error('Expected live buffered document panel to show generated YAML');
}
if (await page.locator('#streamDocument .yaml-key').count() < 3) {
throw new Error('Expected live buffered document panel syntax highlighting');
}
await page.click('#streamButton');
await page.waitForFunction(
() => document.querySelector('#status')?.textContent === 'Passed',
null,
{ timeout: 60000 }
);
const streamingOutput = await page.locator('#output').textContent();
const streamingRunLog = await page.locator('#runLog').textContent();
const streamingDocument = await page.locator('#streamDocument').textContent();
const streamingParsed = parsedStdout(streamingOutput);
if (streamingParsed.transport !== 'SharedArrayBuffer fd_read stdin') throw new Error(streamingOutput);
if (streamingParsed.httpStatusCode !== 200) throw new Error(streamingOutput);
if (streamingParsed.documentCount !== 3000) throw new Error(streamingOutput);
if (streamingParsed.firstName !== 'ci-doc-0') throw new Error(streamingOutput);
if (streamingParsed.lastName !== 'ci-doc-2999') throw new Error(streamingOutput);
if (!Number.isFinite(streamingParsed.stdinReadCount) || streamingParsed.stdinReadCount <= 1) {
throw new Error(`Expected multiple live stdin reads, got ${streamingParsed.stdinReadCount}`);
}
if (!streamingRunLog.includes('Streaming HTTP 200 response body into SharedArrayBuffer') || !streamingRunLog.includes('Network chunk') || !streamingRunLog.includes('Worker fd_read calls') || !streamingRunLog.includes('SharedArrayBuffer fd_read stdin')) {
throw new Error(`Expected live true streaming run log, got: ${streamingRunLog}`);
}
if (!streamingDocument.includes('name: ci-doc-0') || !streamingDocument.includes('name: ci-doc-2999')) {
throw new Error('Expected live streaming document panel to append generated YAML');
}
if (await page.locator('#streamDocument .yaml-key').count() < 3) {
throw new Error('Expected live streaming document panel syntax highlighting');
}
await browser.close();
})();
NODE

37
.github/workflows/wasm.yml vendored Normal file
View File

@ -0,0 +1,37 @@
name: WASM
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install build tools
run: |
sudo apt-get update
sudo apt-get install -y binaryen gzip libcurl4-openssl-dev
- name: Install swiftly
run: |
curl -L https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz > swiftly.tar.gz
tar zxf swiftly.tar.gz
./swiftly init --quiet-shell-followup --assume-yes --skip-install
- name: Install Swift 6.3.2 and Wasm SDK
run: |
. "${SWIFTLY_HOME_DIR:-$HOME/.local/share/swiftly}/env.sh"
swiftly install --use 6.3.2
swiftly run swift sdk install https://download.swift.org/swift-6.3.2-release/wasm-sdk/swift-6.3.2-RELEASE/swift-6.3.2-RELEASE_wasm.artifactbundle.tar.gz --checksum a61f0584c93283589f8b2f42db05c1f9a182b506c2957271402992655591dd7c +6.3.2
- name: Build WASM benchmark site
run: |
. "${SWIFTLY_HOME_DIR:-$HOME/.local/share/swiftly}/env.sh"
bash scripts/build-wasm-site.sh
- name: Upload WASM site artifact
uses: actions/upload-artifact@v6
with:
name: pureyaml-streaming-wasm-site
path: WasmSite
retention-days: 7

24
.github/workflows/windows.yml vendored Normal file
View File

@ -0,0 +1,24 @@
name: Windows
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
jobs:
test:
runs-on: windows-2022
steps:
- uses: actions/checkout@v6
- name: Install Swift
uses: compnerd/gha-setup-swift@main
with:
swift-version: swift-6.3.2-release
swift-build: 6.3.2-RELEASE
- name: Build
shell: pwsh
run: swift build
- name: Test
shell: pwsh
run: swift test

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.build/
.swiftpm/
.DS_Store
WasmSite/*.wasm
WasmSite/*.wasm.gz
WasmSite/vendor/

1
.rules-version Normal file
View File

@ -0,0 +1 @@
1.1.2

12
.swiftformat Normal file
View File

@ -0,0 +1,12 @@
# SwiftFormat configuration. https://github.com/nicklockwood/SwiftFormat
# This is a starting point; adjust to your project, but keep it committed so
# every clone and CI run formats identically.
--swiftversion 6.0
--indent 4
--maxwidth 180
--commas always
--self remove
--importgrouping testable-bottom
--trailingclosures
--exclude .build,Packages/.build,.git

31
.swiftlint.yml Normal file
View File

@ -0,0 +1,31 @@
# SwiftLint configuration. https://github.com/realm/SwiftLint
# A starting point; tune to your project, but keep it committed and run it with
# --strict so warnings cannot accumulate.
disabled_rules:
- todo
opt_in_rules:
- empty_count
- explicit_init
- first_where
- force_unwrapping
- sorted_first_last
- unused_import
line_length:
warning: 180
error: 220
ignores_comments: true
ignores_urls: true
identifier_name:
min_length: 2
type_name:
min_length: 2
excluded:
- .build
- Packages/.build
- .git

55
coi-serviceworker.js Normal file
View File

@ -0,0 +1,55 @@
(() => {
const coop = "same-origin";
const coep = "require-corp";
const reloadKey = "pureyamlStreamingCOIReloadedV3";
if (typeof Window !== "undefined" && self instanceof Window) {
if (window.crossOriginIsolated || !("serviceWorker" in navigator) || !window.isSecureContext) {
return;
}
const reloadForIsolation = () => {
if (!window.crossOriginIsolated && sessionStorage.getItem(reloadKey) !== "1") {
sessionStorage.setItem(reloadKey, "1");
window.location.reload();
}
};
navigator.serviceWorker.addEventListener("controllerchange", reloadForIsolation);
navigator.serviceWorker.register("./coi-serviceworker.js").then(async (registration) => {
await registration.update();
await navigator.serviceWorker.ready;
reloadForIsolation();
}).catch((error) => {
console.warn("Unable to register cross-origin isolation service worker", error);
});
return;
}
self.addEventListener("install", () => {
self.skipWaiting();
});
self.addEventListener("activate", (event) => {
event.waitUntil(self.clients.claim());
});
self.addEventListener("fetch", (event) => {
if (event.request.cache === "only-if-cached" && event.request.mode !== "same-origin") {
return;
}
event.respondWith((async () => {
const response = await fetch(event.request);
const headers = new Headers(response.headers);
headers.set("Cross-Origin-Opener-Policy", coop);
headers.set("Cross-Origin-Embedder-Policy", coep);
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers,
});
})());
});
})();

106
index.html Normal file
View File

@ -0,0 +1,106 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>PureYAMLStreaming WASM Benchmark</title>
<link rel="stylesheet" href="./style.css" />
<script src="./coi-serviceworker.js"></script>
</head>
<body>
<main>
<section class="card">
<p class="eyebrow">PureYAMLStreaming</p>
<h1>Live WASM YAML Benchmark</h1>
<p>
Pick a public real-world YAML file. The browser fetches it, feeds the
bytes to the Swift WASI executable through stdin, and reports
Swift-measured document parse timing live. The Run Log distinguishes
browser buffering from Swift-side chunked scanning.
</p>
<label for="sampleSelect">Public YAML example</label>
<select id="sampleSelect"></select>
<label for="urlInput">URL</label>
<input id="urlInput" type="url" autocomplete="off" />
<button id="runButton" type="button">Run Buffered Benchmark</button>
<button id="streamButton" type="button">Run True Streaming Benchmark</button>
<div id="status" class="status idle">Idle</div>
</section>
<section class="card">
<h2>Streaming Capability</h2>
<dl class="metrics">
<div>
<dt>Isolation</dt>
<dd id="isolationMetric">-</dd>
</div>
<div>
<dt>SharedArrayBuffer</dt>
<dd id="sabMetric">-</dd>
</div>
<div>
<dt>Worker</dt>
<dd id="workerMetric">-</dd>
</div>
<div>
<dt>Mode</dt>
<dd id="modeMetric">-</dd>
</div>
</dl>
</section>
<section class="card">
<h2>Timing</h2>
<dl class="metrics">
<div>
<dt>Bytes</dt>
<dd id="bytesMetric">-</dd>
</div>
<div>
<dt>Fetch</dt>
<dd id="fetchMetric">-</dd>
</div>
<div>
<dt>Swift parse</dt>
<dd id="parseMetric">-</dd>
</div>
<div>
<dt>Throughput</dt>
<dd id="throughputMetric">-</dd>
</div>
<div>
<dt>Documents</dt>
<dd id="documentsMetric">-</dd>
</div>
</dl>
</section>
<section class="card">
<div class="section-header">
<h2>Run Log</h2>
<button id="copyRunLogButton" class="secondary-button" type="button">Copy Run Log</button>
</div>
<div id="runLog" class="run-log" role="log" aria-live="polite">
<div class="log-entry muted">Waiting for benchmark run.</div>
</div>
</section>
<section class="card">
<h2>Document Stream</h2>
<p>
In true streaming mode this panel appends decoded YAML chunks as the
browser receives them. In buffered mode it shows the fetched body after
the browser buffer is complete.
</p>
<pre id="streamDocument" class="stream-document">Waiting for YAML bytes.</pre>
</section>
<section class="card">
<h2>Output</h2>
<pre id="output">Pick a YAML file and choose a benchmark mode.</pre>
</section>
</main>
<script type="module" src="./main.js"></script>
</body>
</html>

747
main.js Normal file
View File

@ -0,0 +1,747 @@
import {
ConsoleStdout,
File,
OpenFile,
WASI,
} from "./vendor/browser_wasi_shim/index.js";
const runButton = document.querySelector("#runButton");
const streamButton = document.querySelector("#streamButton");
const sampleSelect = document.querySelector("#sampleSelect");
const urlInput = document.querySelector("#urlInput");
const statusElement = document.querySelector("#status");
const outputElement = document.querySelector("#output");
const runLogElement = document.querySelector("#runLog");
const copyRunLogButton = document.querySelector("#copyRunLogButton");
const streamDocumentElement = document.querySelector("#streamDocument");
const isolationMetric = document.querySelector("#isolationMetric");
const sabMetric = document.querySelector("#sabMetric");
const workerMetric = document.querySelector("#workerMetric");
const modeMetric = document.querySelector("#modeMetric");
const bytesMetric = document.querySelector("#bytesMetric");
const fetchMetric = document.querySelector("#fetchMetric");
const parseMetric = document.querySelector("#parseMetric");
const throughputMetric = document.querySelector("#throughputMetric");
const documentsMetric = document.querySelector("#documentsMetric");
const params = new URLSearchParams(location.search);
const wasmURL = params.get("wasm") || "./pureyaml-streaming-wasm-smoke.wasm.gz";
let modulePromise = null;
let runStartedAt = performance.now();
const streamControlIndex = {
read: 0,
write: 1,
available: 2,
closed: 3,
error: 4,
sequence: 5,
totalWritten: 6,
readCalls: 7,
waitCount: 8,
};
const streamControlCount = 16;
const streamBufferCapacity = 1024 * 1024;
const documentRenderBudget = 64 * 1024;
let documentRenderQueue = [];
let documentRenderScheduled = false;
let documentRenderResolvers = [];
const samples = [
{
name: "GitHub REST API OpenAPI (8.39 MiB)",
url: "https://api.apis.guru/v2/specs/github.com/1.1.4/openapi.yaml",
},
{
name: "Loket.nl API OpenAPI (10.62 MiB)",
url: "https://api.apis.guru/v2/specs/loket.nl/V2/openapi.yaml",
},
{
name: "Zuora Billing API OpenAPI (6.46 MiB)",
url: "https://api.apis.guru/v2/specs/zuora.com/2021-08-20/openapi.yaml",
},
{
name: "Amazon EC2 OpenAPI (5.36 MiB)",
url: "https://api.apis.guru/v2/specs/amazonaws.com/ec2/2016-11-15/openapi.yaml",
},
{
name: "Zoom API OpenAPI (4.80 MiB)",
url: "https://api.apis.guru/v2/specs/zoom.us/2.0.0/openapi.yaml",
},
{
name: "UniCourt Enterprise APIs OpenAPI (4.29 MiB)",
url: "https://api.apis.guru/v2/specs/unicourt.com/1.0.0/openapi.yaml",
},
{
name: "Kubernetes Swagger (4.26 MiB)",
url: "https://api.apis.guru/v2/specs/kubernetes.io/unversioned/swagger.yaml",
},
{
name: "Autotask PSA Swagger (3.90 MiB)",
url: "https://api.apis.guru/v2/specs/autotask.net/v1/swagger.yaml",
},
{
name: "Google Document AI Warehouse OpenAPI (3.61 MiB)",
url: "https://api.apis.guru/v2/specs/googleapis.com/contentwarehouse/v1/openapi.yaml",
},
{
name: "Stripe API OpenAPI (3.48 MiB)",
url: "https://api.apis.guru/v2/specs/stripe.com/2022-11-15/openapi.yaml",
},
{
name: "Google Compute Engine OpenAPI (3.32 MiB)",
url: "https://api.apis.guru/v2/specs/googleapis.com/compute/v1/openapi.yaml",
},
{
name: "DocuSign REST API OpenAPI (3.14 MiB)",
url: "https://api.apis.guru/v2/specs/docusign.net/v2.1/openapi.yaml",
},
];
for (const sample of samples) {
const option = document.createElement("option");
option.value = sample.url;
option.textContent = sample.name;
sampleSelect.append(option);
}
urlInput.value = samples[0].url;
runButton.addEventListener("click", () => {
runBenchmark().catch((error) => {
renderFailure(error);
});
});
streamButton.addEventListener("click", () => {
runStreamingBenchmark().catch((error) => {
renderFailure(error);
});
});
copyRunLogButton.addEventListener("click", () => {
copyRunLog().catch((error) => {
logStep(`Unable to copy run log: ${String(error?.message || error)}`, "error");
});
});
sampleSelect.addEventListener("change", () => {
urlInput.value = sampleSelect.value;
});
updateStreamingCapabilities();
async function runBenchmark() {
const sourceURL = urlInput.value.trim();
if (!sourceURL) {
throw new Error("Choose or enter a YAML URL first.");
}
runButton.disabled = true;
streamButton.disabled = true;
statusElement.className = "status idle";
statusElement.textContent = "Preparing...";
outputElement.textContent = "";
resetMetrics();
resetLog();
resetStreamDocument("Waiting for buffered YAML fetch to complete.");
logStep("Queued benchmark run.");
logStep(`Selected source: ${sourceURL}`);
logStep(`Loading WASM module: ${wasmURL}`);
const moduleLoadStart = performance.now();
const module = await loadWasmModule(wasmURL);
const moduleLoadMS = performance.now() - moduleLoadStart;
logStep(`WASM module ready in ${formatDuration(moduleLoadMS)}.`);
statusElement.textContent = "Fetching YAML...";
logStep("Fetching YAML bytes in the browser. This fetch is buffered before WASI starts.");
const fetchStart = performance.now();
const response = await fetch(sourceURL, { cache: "no-store" });
if (!response.ok) {
throw new Error(`Failed to fetch YAML: HTTP ${response.status}`);
}
const yamlBytes = new Uint8Array(await response.arrayBuffer());
const fetchMS = performance.now() - fetchStart;
replaceStreamDocument(decodeYAMLBytes(yamlBytes));
await waitForDocumentFlush();
await nextPaint();
logStep(`Fetched HTTP ${response.status}, ${formatBytes(yamlBytes.byteLength)} in ${formatDuration(fetchMS)}.`);
logStep("Handing buffered response to SwiftWASIHTTPClient.HostHTTPClient through WASI stdin.");
logStep("Preparing WASI file descriptors.");
let stdoutText = "";
let stderrText = "";
const fds = [
new OpenFile(new File(yamlBytes)),
ConsoleStdout.lineBuffered((line) => {
stdoutText += `${line}\n`;
}),
ConsoleStdout.lineBuffered((line) => {
stderrText += `${line}\n`;
}),
];
logStep("Creating WASI instance.");
const wasi = new WASI(["pureyaml-streaming-wasm-smoke", sourceURL, String(response.status)], [], fds);
statusElement.textContent = "Running Swift parser...";
logStep("Starting Swift parser. The browser tab can pause while WebAssembly is executing.");
await nextPaint();
const parseStart = performance.now();
const instance = await WebAssembly.instantiate(module, {
wasi_snapshot_preview1: wasi.wasiImport,
});
logStep("WebAssembly instance created; entering WASI _start.");
await nextPaint();
wasi.start(instance);
const wallClockParseMS = performance.now() - parseStart;
logStep(`WASI process returned in ${formatDuration(wallClockParseMS)}.`);
logStep("Parsing Swift JSON result from stdout.");
const parsed = parseSmokeOutput(stdoutText);
const ok = parsed?.ok === true;
if (!Number.isFinite(parsed?.parseMilliseconds)) {
throw new Error("WASM output did not include a finite Swift parseMilliseconds value.");
}
const parseMS = parsed.parseMilliseconds;
const throughput = parseMS > 0 ? yamlBytes.byteLength / (parseMS / 1000) : 0;
if (!Number.isFinite(parsed?.chunkCount) || !Number.isFinite(parsed?.chunkSize)) {
throw new Error("WASM output did not include finite Swift chunk instrumentation.");
}
logStep(`Swift transport: ${parsed.transport || "unknown"}; HTTP status: ${parsed.httpStatusCode ?? "n/a"}.`);
logStep(`Swift parse reported ${formatDuration(parseMS)}.`);
logStep(
`Swift ChunkedUTF8Reader consumed ${parsed.chunkCount} chunk(s) at chunkSize ${formatBytes(parsed.chunkSize)}.`,
);
logStep(`Largest Swift chunk observed: ${formatBytes(parsed.maxChunkByteCount ?? 0)}.`);
logStep(`Document scanner emitted ${parsed.scannerDocumentSourceCount ?? 0} document source(s).`);
logStep(`Parsed ${parsed.documentCount ?? 0} document(s).`);
logStep(`Throughput: ${formatBytes(throughput)}/s.`);
statusElement.className = ok ? "status pass" : "status fail";
statusElement.textContent = ok ? "Passed" : "Failed";
bytesMetric.textContent = formatBytes(yamlBytes.byteLength);
fetchMetric.textContent = formatDuration(fetchMS);
parseMetric.textContent = formatDuration(parseMS);
throughputMetric.textContent = `${formatBytes(throughput)}/s`;
documentsMetric.textContent = parsed?.documentCount ?? "-";
outputElement.textContent = [
`url: ${sourceURL}`,
`moduleLoad: ${formatDuration(moduleLoadMS)}`,
`fetch: ${formatDuration(fetchMS)}`,
`swiftParse: ${formatDuration(parseMS)}`,
`processWallClock: ${formatDuration(wallClockParseMS)}`,
`throughput: ${formatBytes(throughput)}/s`,
"",
"stdout:",
stdoutText || "(empty)",
"",
"stderr:",
stderrText || "(empty)",
].join("\n");
logStep(ok ? "Benchmark completed successfully." : "Benchmark finished with parser failure.");
resetButtons();
}
async function runStreamingBenchmark() {
const sourceURL = urlInput.value.trim();
if (!sourceURL) {
throw new Error("Choose or enter a YAML URL first.");
}
if (!streamingSupported()) {
throw new Error("True streaming mode requires cross-origin isolation, SharedArrayBuffer, Worker, and Atomics.wait.");
}
runButton.disabled = true;
streamButton.disabled = true;
statusElement.className = "status idle";
statusElement.textContent = "Streaming...";
outputElement.textContent = "";
resetMetrics();
resetLog();
resetStreamDocument("Waiting for streaming YAML chunks.");
logStep("Queued true browser-to-WASI streaming run.");
logStep(`Selected source: ${sourceURL}`);
const sharedBuffer = new SharedArrayBuffer(
streamControlCount * Int32Array.BYTES_PER_ELEMENT + streamBufferCapacity,
);
const control = new Int32Array(sharedBuffer, 0, streamControlCount);
const bytes = new Uint8Array(sharedBuffer, streamControlCount * Int32Array.BYTES_PER_ELEMENT, streamBufferCapacity);
let worker = null;
let stdoutText = "";
let stderrText = "";
let workerDone;
const workerDonePromise = new Promise((resolve, reject) => {
workerDone = { resolve, reject };
});
logStep("Starting Worker-owned WASI runtime.");
const fetchStart = performance.now();
const response = await fetch(sourceURL, { cache: "no-store" });
if (!response.ok) {
throw new Error(`Failed to fetch YAML: HTTP ${response.status}`);
}
if (!response.body) {
throw new Error("This browser did not expose a ReadableStream response body.");
}
worker = new Worker("./stream-worker.js", { type: "module" });
worker.addEventListener("message", (event) => {
const message = event.data || {};
if (message.type === "log") {
logStep(message.message);
} else if (message.type === "stdout") {
stdoutText += `${message.line}\n`;
} else if (message.type === "stderr") {
stderrText += `${message.line}\n`;
} else if (message.type === "done") {
stdoutText = message.stdoutText;
stderrText = message.stderrText;
logStep(`Worker fd_read calls: ${message.readCalls}; ring-buffer waits: ${message.waitCount}.`);
workerDone.resolve(message);
} else if (message.type === "error") {
workerDone.reject(new Error(message.message));
}
});
worker.postMessage({
type: "start",
wasmURL,
sourceURL,
statusCode: response.status,
sharedBuffer,
capacity: streamBufferCapacity,
});
logStep(`Streaming HTTP ${response.status} response body into SharedArrayBuffer.`);
const reader = response.body.getReader();
const documentDecoder = new TextDecoder();
let networkChunkCount = 0;
let fetchedByteCount = 0;
try {
while (true) {
const { value, done } = await reader.read();
if (done) {
break;
}
networkChunkCount += 1;
fetchedByteCount += value.byteLength;
appendStreamDocumentChunk(documentDecoder.decode(value, { stream: true }));
await writeToRingBuffer(value, control, bytes);
logStep(`Network chunk ${networkChunkCount}: wrote ${formatBytes(value.byteLength)} to ring buffer.`);
}
} catch (error) {
Atomics.store(control, streamControlIndex.error, 1);
Atomics.store(control, streamControlIndex.closed, 1);
wakeRingBuffer(control);
worker?.terminate();
throw error;
}
appendStreamDocumentChunk(documentDecoder.decode());
await waitForDocumentFlush();
Atomics.store(control, streamControlIndex.closed, 1);
wakeRingBuffer(control);
const fetchMS = performance.now() - fetchStart;
logStep(`Network stream finished: ${networkChunkCount} chunk(s), ${formatBytes(fetchedByteCount)} in ${formatDuration(fetchMS)}.`);
let workerResult;
try {
workerResult = await workerDonePromise;
} catch (error) {
worker?.terminate();
throw error;
}
worker?.terminate();
renderBenchmarkResult({
sourceURL,
moduleLoadMS: workerResult.moduleLoadMS,
fetchMS,
wallClockParseMS: workerResult.processWallClockMS,
stdoutText,
stderrText,
inputByteCount: fetchedByteCount,
workerResult,
modeLabel: "true streaming",
});
}
function renderBenchmarkResult({
sourceURL,
moduleLoadMS,
fetchMS,
wallClockParseMS,
stdoutText,
stderrText,
inputByteCount,
modeLabel,
}) {
logStep("Parsing Swift JSON result from stdout.");
const parsed = parseSmokeOutput(stdoutText);
const ok = parsed?.ok === true;
if (!Number.isFinite(parsed?.parseMilliseconds)) {
throw new Error("WASM output did not include a finite Swift parseMilliseconds value.");
}
const parseMS = parsed.parseMilliseconds;
const throughput = parseMS > 0 ? inputByteCount / (parseMS / 1000) : 0;
if (!Number.isFinite(parsed?.chunkCount) || !Number.isFinite(parsed?.chunkSize)) {
throw new Error("WASM output did not include finite Swift chunk instrumentation.");
}
logStep(`Swift transport: ${parsed.transport || "unknown"}; HTTP status: ${parsed.httpStatusCode ?? "n/a"}.`);
logStep(`Swift parse reported ${formatDuration(parseMS)}.`);
logStep(
`Swift ChunkedUTF8Reader consumed ${parsed.chunkCount} chunk(s) at chunkSize ${formatBytes(parsed.chunkSize)}.`,
);
logStep(`Largest Swift chunk observed: ${formatBytes(parsed.maxChunkByteCount ?? 0)}.`);
logStep(`Document scanner emitted ${parsed.scannerDocumentSourceCount ?? 0} document source(s).`);
logStep(`Swift stdin read calls: ${parsed.stdinReadCount ?? "n/a"}; max stdin read: ${formatBytes(parsed.maxStdinReadByteCount ?? 0)}.`);
logStep(`Parsed ${parsed.documentCount ?? 0} document(s).`);
logStep(`Throughput: ${formatBytes(throughput)}/s.`);
statusElement.className = ok ? "status pass" : "status fail";
statusElement.textContent = ok ? "Passed" : "Failed";
bytesMetric.textContent = formatBytes(inputByteCount);
fetchMetric.textContent = formatDuration(fetchMS);
parseMetric.textContent = formatDuration(parseMS);
throughputMetric.textContent = `${formatBytes(throughput)}/s`;
documentsMetric.textContent = parsed?.documentCount ?? "-";
outputElement.textContent = [
`url: ${sourceURL}`,
`mode: ${modeLabel}`,
`moduleLoad: ${formatDuration(moduleLoadMS)}`,
`fetch: ${formatDuration(fetchMS)}`,
`swiftParse: ${formatDuration(parseMS)}`,
`processWallClock: ${formatDuration(wallClockParseMS)}`,
`throughput: ${formatBytes(throughput)}/s`,
"",
"stdout:",
stdoutText || "(empty)",
"",
"stderr:",
stderrText || "(empty)",
].join("\n");
logStep(ok ? "Benchmark completed successfully." : "Benchmark finished with parser failure.");
resetButtons();
}
async function writeToRingBuffer(chunk, control, bytes) {
let offset = 0;
while (offset < chunk.byteLength) {
const available = Atomics.load(control, streamControlIndex.available);
if (available >= streamBufferCapacity) {
logStep("Ring buffer full; fetch pump waiting for Worker fd_read to resume.", "muted");
await waitForRingSpace(control);
logStep("Ring buffer has capacity again; fetch pump resumed.", "muted");
continue;
}
const write = Atomics.load(control, streamControlIndex.write);
const space = streamBufferCapacity - available;
const byteCount = Math.min(chunk.byteLength - offset, space, streamBufferCapacity - write);
bytes.set(chunk.subarray(offset, offset + byteCount), write);
Atomics.store(control, streamControlIndex.write, (write + byteCount) % streamBufferCapacity);
Atomics.add(control, streamControlIndex.available, byteCount);
Atomics.add(control, streamControlIndex.totalWritten, byteCount);
offset += byteCount;
wakeRingBuffer(control);
}
}
async function waitForRingSpace(control) {
while (Atomics.load(control, streamControlIndex.available) >= streamBufferCapacity) {
await new Promise((resolve) => setTimeout(resolve, 4));
}
}
function wakeRingBuffer(control) {
Atomics.add(control, streamControlIndex.sequence, 1);
Atomics.notify(control, streamControlIndex.sequence);
}
async function loadWasmModule(url) {
if (!modulePromise) {
modulePromise = loadWasmBytes(url).then((bytes) => WebAssembly.compile(bytes));
}
return modulePromise;
}
async function loadWasmBytes(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch WASM: HTTP ${response.status}`);
}
const bytes = await response.arrayBuffer();
if (!url.endsWith(".gz")) {
return bytes;
}
if (!("DecompressionStream" in globalThis)) {
throw new Error("This browser does not support DecompressionStream.");
}
const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream("gzip"));
return new Response(stream).arrayBuffer();
}
function parseSmokeOutput(text) {
try {
return JSON.parse(text);
} catch {
return null;
}
}
function renderFailure(error) {
statusElement.className = "status fail";
statusElement.textContent = "Failed";
outputElement.textContent = String(error?.stack || error);
logStep(`Failed: ${String(error?.message || error)}`, "error");
resetButtons();
}
function resetButtons() {
runButton.disabled = false;
streamButton.disabled = !streamingSupported();
}
function updateStreamingCapabilities() {
const isolated = crossOriginIsolated === true;
const hasSharedArrayBuffer = "SharedArrayBuffer" in globalThis;
const hasWorker = "Worker" in globalThis;
const hasAtomicsWait = typeof Atomics?.wait === "function";
isolationMetric.textContent = isolated ? "yes" : "no";
sabMetric.textContent = hasSharedArrayBuffer ? "yes" : "no";
workerMetric.textContent = hasWorker && hasAtomicsWait ? "yes" : "no";
modeMetric.textContent = streamingSupported() ? "streaming enabled" : "buffered fallback";
resetButtons();
}
function streamingSupported() {
return crossOriginIsolated === true
&& "SharedArrayBuffer" in globalThis
&& "Worker" in globalThis
&& typeof Atomics?.wait === "function";
}
function resetMetrics() {
bytesMetric.textContent = "-";
fetchMetric.textContent = "-";
parseMetric.textContent = "-";
throughputMetric.textContent = "-";
documentsMetric.textContent = "-";
}
async function copyRunLog() {
const text = runLogElement.innerText.trim();
if (!text) {
return;
}
try {
if ("clipboard" in navigator && window.isSecureContext) {
await navigator.clipboard.writeText(text);
} else {
copyTextWithTextarea(text);
}
} catch {
copyTextWithTextarea(text);
}
const previousLabel = copyRunLogButton.textContent;
copyRunLogButton.textContent = "Copied";
window.setTimeout(() => {
copyRunLogButton.textContent = previousLabel;
}, 1500);
}
function copyTextWithTextarea(text) {
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.setAttribute("readonly", "");
textarea.style.position = "fixed";
textarea.style.opacity = "0";
document.body.append(textarea);
textarea.select();
document.execCommand("copy");
textarea.remove();
}
function resetStreamDocument(message) {
documentRenderQueue = [];
documentRenderResolvers.splice(0).forEach((resolve) => resolve());
streamDocumentElement.replaceChildren(document.createTextNode(message));
streamDocumentElement.scrollTop = 0;
}
function replaceStreamDocument(text) {
documentRenderQueue = [];
documentRenderResolvers.splice(0).forEach((resolve) => resolve());
streamDocumentElement.replaceChildren();
appendStreamDocumentChunk(text || "(empty document)");
streamDocumentElement.scrollTop = 0;
}
function appendStreamDocumentChunk(text) {
if (!text) {
return;
}
if (streamDocumentElement.childNodes.length === 1 && streamDocumentElement.textContent.startsWith("Waiting for")) {
streamDocumentElement.replaceChildren();
}
documentRenderQueue.push(text);
scheduleDocumentFlush();
}
function decodeYAMLBytes(bytes) {
return new TextDecoder().decode(bytes);
}
function scheduleDocumentFlush() {
if (documentRenderScheduled) {
return;
}
documentRenderScheduled = true;
requestAnimationFrame(flushDocumentRenderQueue);
}
function flushDocumentRenderQueue() {
documentRenderScheduled = false;
let remainingBudget = documentRenderBudget;
const fragment = document.createDocumentFragment();
while (documentRenderQueue.length > 0 && remainingBudget > 0) {
const next = documentRenderQueue.shift();
const piece = next.length > remainingBudget ? next.slice(0, remainingBudget) : next;
const rest = next.length > remainingBudget ? next.slice(remainingBudget) : "";
fragment.append(renderYAMLSyntax(piece));
remainingBudget -= piece.length;
if (rest) {
documentRenderQueue.unshift(rest);
}
}
streamDocumentElement.append(fragment);
streamDocumentElement.scrollTop = streamDocumentElement.scrollHeight;
if (documentRenderQueue.length > 0) {
scheduleDocumentFlush();
return;
}
documentRenderResolvers.splice(0).forEach((resolve) => resolve());
}
function waitForDocumentFlush() {
if (!documentRenderScheduled && documentRenderQueue.length === 0) {
return Promise.resolve();
}
return new Promise((resolve) => {
documentRenderResolvers.push(resolve);
});
}
function renderYAMLSyntax(text) {
const fragment = document.createDocumentFragment();
for (const segment of text.split(/(\r?\n)/)) {
if (segment === "\n" || segment === "\r\n") {
fragment.append(document.createTextNode(segment));
} else {
renderYAMLLine(segment, fragment);
}
}
return fragment;
}
function renderYAMLLine(line, fragment) {
const commentIndex = findYAMLCommentIndex(line);
const source = commentIndex >= 0 ? line.slice(0, commentIndex) : line;
const comment = commentIndex >= 0 ? line.slice(commentIndex) : "";
const keyMatch = source.match(/^(\s*(?:-\s*)?)([A-Za-z0-9_.-]+)(\s*:)(.*)$/);
if (source.trim() === "---" || source.trim() === "...") {
appendSyntaxSpan(fragment, source, "yaml-marker");
} else if (keyMatch) {
fragment.append(document.createTextNode(keyMatch[1]));
appendSyntaxSpan(fragment, keyMatch[2], "yaml-key");
appendSyntaxSpan(fragment, keyMatch[3], "yaml-punctuation");
appendYAMLValue(fragment, keyMatch[4]);
} else {
appendYAMLValue(fragment, source);
}
if (comment) {
appendSyntaxSpan(fragment, comment, "yaml-comment");
}
}
function appendYAMLValue(fragment, value) {
const trimmed = value.trim();
const leading = value.slice(0, value.length - value.trimStart().length);
const trailingStart = value.length - value.trimEnd().length;
const trailing = trailingStart > 0 ? value.slice(value.length - trailingStart) : "";
if (leading) {
fragment.append(document.createTextNode(leading));
}
if (/^(true|false|null|~)$/i.test(trimmed)) {
appendSyntaxSpan(fragment, trimmed, "yaml-literal");
} else if (/^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i.test(trimmed)) {
appendSyntaxSpan(fragment, trimmed, "yaml-number");
} else if (/^(['"]).*\1$/.test(trimmed)) {
appendSyntaxSpan(fragment, trimmed, "yaml-string");
} else {
fragment.append(document.createTextNode(trimmed));
}
if (trailing) {
fragment.append(document.createTextNode(trailing));
}
}
function appendSyntaxSpan(fragment, text, className) {
const span = document.createElement("span");
span.className = className;
span.textContent = text;
fragment.append(span);
}
function findYAMLCommentIndex(line) {
let quote = null;
for (let index = 0; index < line.length; index += 1) {
const character = line[index];
if ((character === "'" || character === "\"") && line[index - 1] !== "\\") {
quote = quote === character ? null : quote || character;
}
if (character === "#" && quote === null && (index === 0 || /\s/.test(line[index - 1]))) {
return index;
}
}
return -1;
}
function formatDuration(milliseconds) {
if (milliseconds < 1000) {
return `${milliseconds.toFixed(1)} ms`;
}
return `${(milliseconds / 1000).toFixed(3)} s`;
}
function formatBytes(byteCount) {
if (byteCount < 1024) {
return `${byteCount.toFixed(0)} B`;
}
if (byteCount < 1024 * 1024) {
return `${(byteCount / 1024).toFixed(1)} KiB`;
}
return `${(byteCount / 1024 / 1024).toFixed(2)} MiB`;
}
function resetLog() {
runStartedAt = performance.now();
runLogElement.replaceChildren();
}
function logStep(message, level = "info") {
const entry = document.createElement("div");
entry.className = `log-entry ${level}`;
const elapsed = (performance.now() - runStartedAt) / 1000;
entry.textContent = `[+${elapsed.toFixed(3)}s] ${message}`;
runLogElement.append(entry);
runLogElement.scrollTop = runLogElement.scrollHeight;
}
function nextPaint() {
return new Promise((resolve) => {
requestAnimationFrame(() => {
requestAnimationFrame(resolve);
});
});
}

Binary file not shown.

126
stream-worker.js Normal file
View File

@ -0,0 +1,126 @@
import {
ConsoleStdout,
File,
OpenFile,
WASI,
} from "./vendor/browser_wasi_shim/index.js";
const controlIndex = {
read: 0,
write: 1,
available: 2,
closed: 3,
error: 4,
sequence: 5,
totalWritten: 6,
readCalls: 7,
waitCount: 8,
};
self.addEventListener("message", (event) => {
if (event.data?.type !== "start") {
return;
}
runStreamingBenchmark(event.data).catch((error) => {
self.postMessage({
type: "error",
message: String(error?.stack || error),
});
});
});
async function runStreamingBenchmark(config) {
const moduleLoadStart = performance.now();
const module = await loadWasmModule(config.wasmURL);
const moduleLoadMS = performance.now() - moduleLoadStart;
self.postMessage({ type: "log", message: `Worker loaded WASM module in ${moduleLoadMS.toFixed(1)} ms.` });
let stdoutText = "";
let stderrText = "";
const stdin = new StreamingStdin(config.sharedBuffer, config.capacity);
const wasi = new WASI(
["pureyaml-streaming-wasm-smoke", "--stream-stdin", config.sourceURL, String(config.statusCode)],
[],
[
stdin,
ConsoleStdout.lineBuffered((line) => {
stdoutText += `${line}\n`;
self.postMessage({ type: "stdout", line });
}),
ConsoleStdout.lineBuffered((line) => {
stderrText += `${line}\n`;
self.postMessage({ type: "stderr", line });
}),
],
);
self.postMessage({ type: "log", message: "Worker instantiated WASI runtime." });
const processStart = performance.now();
const instance = await WebAssembly.instantiate(module, {
wasi_snapshot_preview1: wasi.wasiImport,
});
self.postMessage({ type: "log", message: "Worker entering WASI _start with streaming stdin." });
wasi.start(instance);
const processWallClockMS = performance.now() - processStart;
self.postMessage({
type: "done",
stdoutText,
stderrText,
moduleLoadMS,
processWallClockMS,
readCalls: Atomics.load(stdin.control, controlIndex.readCalls),
waitCount: Atomics.load(stdin.control, controlIndex.waitCount),
});
}
async function loadWasmModule(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch WASM in worker: HTTP ${response.status}`);
}
const bytes = await response.arrayBuffer();
if (!url.endsWith(".gz")) {
return WebAssembly.compile(bytes);
}
if (!("DecompressionStream" in globalThis)) {
throw new Error("This browser does not support DecompressionStream in workers.");
}
const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream("gzip"));
return WebAssembly.compile(await new Response(stream).arrayBuffer());
}
class StreamingStdin extends OpenFile {
constructor(sharedBuffer, capacity) {
super(new File([]));
this.control = new Int32Array(sharedBuffer, 0, 16);
this.bytes = new Uint8Array(sharedBuffer, 16 * Int32Array.BYTES_PER_ELEMENT, capacity);
this.capacity = capacity;
}
fd_read(length) {
const data = this.readBlocking(length);
return { ret: 0, data };
}
readBlocking(length) {
while (Atomics.load(this.control, controlIndex.available) === 0) {
if (Atomics.load(this.control, controlIndex.closed) === 1 || Atomics.load(this.control, controlIndex.error) === 1) {
return new Uint8Array();
}
Atomics.add(this.control, controlIndex.waitCount, 1);
const sequence = Atomics.load(this.control, controlIndex.sequence);
Atomics.wait(this.control, controlIndex.sequence, sequence);
}
const available = Atomics.load(this.control, controlIndex.available);
const read = Atomics.load(this.control, controlIndex.read);
const byteCount = Math.min(length, available, this.capacity - read);
const result = this.bytes.slice(read, read + byteCount);
Atomics.store(this.control, controlIndex.read, (read + byteCount) % this.capacity);
Atomics.sub(this.control, controlIndex.available, byteCount);
Atomics.add(this.control, controlIndex.readCalls, 1);
Atomics.add(this.control, controlIndex.sequence, 1);
Atomics.notify(this.control, controlIndex.sequence);
return result;
}
}

243
style.css Normal file
View File

@ -0,0 +1,243 @@
:root {
color-scheme: light dark;
font-family:
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
sans-serif;
background: Canvas;
color: CanvasText;
}
body {
margin: 0;
min-height: 100vh;
padding: 28px 0;
}
main {
width: min(1040px, calc(100vw - 32px));
margin: 0 auto;
display: grid;
gap: 20px;
}
.card {
border: 1px solid color-mix(in srgb, CanvasText 18%, transparent);
border-radius: 20px;
padding: 28px;
background: color-mix(in srgb, Canvas 92%, CanvasText 8%);
box-shadow: 0 24px 80px color-mix(in srgb, CanvasText 12%, transparent);
}
.eyebrow {
margin: 0 0 10px;
text-transform: uppercase;
letter-spacing: 0.12em;
font-size: 12px;
opacity: 0.7;
}
h1,
h2 {
margin: 0 0 12px;
line-height: 1.05;
}
h1 {
font-size: clamp(36px, 7vw, 72px);
}
p {
max-width: 720px;
line-height: 1.6;
opacity: 0.82;
}
label {
display: block;
margin: 18px 0 8px;
font-weight: 800;
}
select,
input {
box-sizing: border-box;
width: 100%;
border: 1px solid color-mix(in srgb, CanvasText 18%, transparent);
border-radius: 14px;
padding: 12px 14px;
background: Canvas;
color: CanvasText;
font: inherit;
}
button {
appearance: none;
border: 0;
border-radius: 999px;
margin-top: 18px;
margin-right: 10px;
padding: 12px 18px;
font: inherit;
font-weight: 700;
cursor: pointer;
background: CanvasText;
color: Canvas;
}
#streamButton {
border: 1px solid CanvasText;
background: Canvas;
color: CanvasText;
}
.secondary-button {
margin-top: 0;
border: 1px solid CanvasText;
background: Canvas;
color: CanvasText;
}
button:disabled {
cursor: wait;
opacity: 0.55;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
margin-bottom: 20px;
}
.section-header h2 {
margin-bottom: 0;
}
.status {
margin-top: 18px;
display: inline-flex;
border-radius: 999px;
padding: 8px 12px;
font-weight: 700;
background: color-mix(in srgb, CanvasText 10%, transparent);
}
.status.pass {
background: color-mix(in srgb, #008a45 30%, transparent);
}
.status.fail {
background: color-mix(in srgb, #c21f39 30%, transparent);
}
.metrics {
margin: 0;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 14px;
}
.metrics div {
border: 1px solid color-mix(in srgb, CanvasText 14%, transparent);
border-radius: 16px;
padding: 16px;
background: color-mix(in srgb, Canvas 96%, CanvasText 4%);
}
dt {
margin: 0 0 8px;
font-size: 12px;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
opacity: 0.65;
}
dd {
margin: 0;
font: 700 24px/1.1 ui-monospace, SFMono-Regular, Menlo, monospace;
}
#isolationMetric,
#sabMetric,
#workerMetric,
#modeMetric {
font-size: 16px;
}
.run-log {
height: min(320px, 45vh);
overflow-y: auto;
border: 1px solid color-mix(in srgb, CanvasText 14%, transparent);
border-radius: 16px;
padding: 14px;
background: color-mix(in srgb, Canvas 96%, CanvasText 4%);
font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
}
.stream-document {
height: min(420px, 55vh);
overflow: auto;
border: 1px solid color-mix(in srgb, CanvasText 14%, transparent);
border-radius: 16px;
padding: 14px;
background: color-mix(in srgb, Canvas 96%, CanvasText 4%);
font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
}
.yaml-key {
color: #4f8cff;
font-weight: 700;
}
.yaml-punctuation,
.yaml-marker {
color: #b66cff;
font-weight: 700;
}
.yaml-comment {
color: #708090;
font-style: italic;
}
.yaml-string {
color: #138a36;
}
.yaml-number {
color: #b36b00;
}
.yaml-literal {
color: #c21f39;
font-weight: 700;
}
.log-entry {
padding: 6px 0;
border-bottom: 1px solid color-mix(in srgb, CanvasText 8%, transparent);
overflow-wrap: anywhere;
}
.log-entry:last-child {
border-bottom: 0;
}
.log-entry.muted {
opacity: 0.65;
}
.log-entry.error {
color: #c21f39;
font-weight: 700;
}
pre {
margin: 0;
white-space: pre-wrap;
overflow-wrap: anywhere;
line-height: 1.5;
}

1
vendor/browser_wasi_shim/debug.js vendored Normal file
View File

@ -0,0 +1 @@
let Debug=class Debug{enable(enabled){this.log=createLogger(enabled===undefined?true:enabled,this.prefix)}get enabled(){return this.isEnabled}constructor(isEnabled){this.isEnabled=isEnabled;this.prefix="wasi:";this.enable(isEnabled)}};function createLogger(enabled,prefix){if(enabled){const a=console.log.bind(console,"%c%s","color: #265BA0",prefix);return a}else{return()=>{}}}export const debug=new Debug(false);

1
vendor/browser_wasi_shim/fd.js vendored Normal file
View File

@ -0,0 +1 @@
import*as wasi from"./wasi_defs.js";export class Fd{fd_allocate(offset,len){return wasi.ERRNO_NOTSUP}fd_close(){return 0}fd_fdstat_get(){return{ret:wasi.ERRNO_NOTSUP,fdstat:null}}fd_fdstat_set_flags(flags){return wasi.ERRNO_NOTSUP}fd_fdstat_set_rights(fs_rights_base,fs_rights_inheriting){return wasi.ERRNO_NOTSUP}fd_filestat_get(){return{ret:wasi.ERRNO_NOTSUP,filestat:null}}fd_filestat_set_size(size){return wasi.ERRNO_NOTSUP}fd_filestat_set_times(atim,mtim,fst_flags){return wasi.ERRNO_NOTSUP}fd_pread(size,offset){return{ret:wasi.ERRNO_NOTSUP,data:new Uint8Array}}fd_prestat_get(){return{ret:wasi.ERRNO_NOTSUP,prestat:null}}fd_pwrite(data,offset){return{ret:wasi.ERRNO_NOTSUP,nwritten:0}}fd_read(size){return{ret:wasi.ERRNO_NOTSUP,data:new Uint8Array}}fd_readdir_single(cookie){return{ret:wasi.ERRNO_NOTSUP,dirent:null}}fd_seek(offset,whence){return{ret:wasi.ERRNO_NOTSUP,offset:0n}}fd_sync(){return 0}fd_tell(){return{ret:wasi.ERRNO_NOTSUP,offset:0n}}fd_write(data){return{ret:wasi.ERRNO_NOTSUP,nwritten:0}}path_create_directory(path){return wasi.ERRNO_NOTSUP}path_filestat_get(flags,path){return{ret:wasi.ERRNO_NOTSUP,filestat:null}}path_filestat_set_times(flags,path,atim,mtim,fst_flags){return wasi.ERRNO_NOTSUP}path_link(path,inode,allow_dir){return wasi.ERRNO_NOTSUP}path_unlink(path){return{ret:wasi.ERRNO_NOTSUP,inode_obj:null}}path_lookup(path,dirflags){return{ret:wasi.ERRNO_NOTSUP,inode_obj:null}}path_open(dirflags,path,oflags,fs_rights_base,fs_rights_inheriting,fd_flags){return{ret:wasi.ERRNO_NOTDIR,fd_obj:null}}path_readlink(path){return{ret:wasi.ERRNO_NOTSUP,data:null}}path_remove_directory(path){return wasi.ERRNO_NOTSUP}path_rename(old_path,new_fd,new_path){return wasi.ERRNO_NOTSUP}path_unlink_file(path){return wasi.ERRNO_NOTSUP}}export class Inode{}

1
vendor/browser_wasi_shim/fs_mem.js vendored Normal file

File diff suppressed because one or more lines are too long

1
vendor/browser_wasi_shim/fs_opfs.js vendored Normal file
View File

@ -0,0 +1 @@
import*as wasi from"./wasi_defs.js";import{Fd,Inode}from"./fd.js";export class SyncOPFSFile extends Inode{path_open(oflags,fs_rights_base,fd_flags){if(this.readonly&&(fs_rights_base&BigInt(wasi.RIGHTS_FD_WRITE))==BigInt(wasi.RIGHTS_FD_WRITE)){return{ret:wasi.ERRNO_PERM,fd_obj:null}}if((oflags&wasi.OFLAGS_TRUNC)==wasi.OFLAGS_TRUNC){if(this.readonly)return{ret:wasi.ERRNO_PERM,fd_obj:null};this.handle.truncate(0)}const file=new OpenSyncOPFSFile(this);if(fd_flags&wasi.FDFLAGS_APPEND)file.fd_seek(0n,wasi.WHENCE_END);return{ret:wasi.ERRNO_SUCCESS,fd_obj:file}}get size(){return BigInt(this.handle.getSize())}stat(){return new wasi.Filestat(wasi.FILETYPE_REGULAR_FILE,this.size)}constructor(handle,options){super();this.handle=handle;this.readonly=!!options?.readonly}}export class OpenSyncOPFSFile extends Fd{fd_allocate(offset,len){if(BigInt(this.file.handle.getSize())>offset+len){}else{this.file.handle.truncate(Number(offset+len))}return wasi.ERRNO_SUCCESS}fd_fdstat_get(){return{ret:0,fdstat:new wasi.Fdstat(wasi.FILETYPE_REGULAR_FILE,0)}}fd_filestat_get(){return{ret:0,filestat:new wasi.Filestat(wasi.FILETYPE_REGULAR_FILE,BigInt(this.file.handle.getSize()))}}fd_filestat_set_size(size){this.file.handle.truncate(Number(size));return wasi.ERRNO_SUCCESS}fd_read(size){const buf=new Uint8Array(size);const n=this.file.handle.read(buf,{at:Number(this.position)});this.position+=BigInt(n);return{ret:0,data:buf.slice(0,n)}}fd_seek(offset,whence){let calculated_offset;switch(whence){case wasi.WHENCE_SET:calculated_offset=BigInt(offset);break;case wasi.WHENCE_CUR:calculated_offset=this.position+BigInt(offset);break;case wasi.WHENCE_END:calculated_offset=BigInt(this.file.handle.getSize())+BigInt(offset);break;default:return{ret:wasi.ERRNO_INVAL,offset:0n}}if(calculated_offset<0){return{ret:wasi.ERRNO_INVAL,offset:0n}}this.position=calculated_offset;return{ret:wasi.ERRNO_SUCCESS,offset:this.position}}fd_write(data){if(this.file.readonly)return{ret:wasi.ERRNO_BADF,nwritten:0};const n=this.file.handle.write(data,{at:Number(this.position)});this.position+=BigInt(n);return{ret:wasi.ERRNO_SUCCESS,nwritten:n}}fd_sync(){this.file.handle.flush();return wasi.ERRNO_SUCCESS}constructor(file){super();this.position=0n;this.file=file}}

1
vendor/browser_wasi_shim/index.js vendored Normal file
View File

@ -0,0 +1 @@
import WASI,{WASIProcExit}from"./wasi.js";export{WASI,WASIProcExit};export{Fd,Inode}from"./fd.js";export{File,Directory,OpenFile,OpenDirectory,PreopenDirectory,ConsoleStdout}from"./fs_mem.js";export{SyncOPFSFile,OpenSyncOPFSFile}from"./fs_opfs.js";export{strace}from"./strace.js";export*as wasi from"./wasi_defs.js";

1
vendor/browser_wasi_shim/strace.js vendored Normal file
View File

@ -0,0 +1 @@
export function strace(imports,no_trace){return new Proxy(imports,{get(target,prop,receiver){const f=Reflect.get(target,prop,receiver);if(no_trace.includes(prop)){return f}return function(...args){console.log(prop,"(",...args,")");const result=Reflect.apply(f,receiver,args);console.log(" =",result);return result}}})}

1
vendor/browser_wasi_shim/wasi.js vendored Normal file

File diff suppressed because one or more lines are too long

1
vendor/browser_wasi_shim/wasi_defs.js vendored Normal file

File diff suppressed because one or more lines are too long