phase 5
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
# spikes/wasm-phase5 — parity, audit, and performance harness
|
||||
|
||||
Phase 5's production exit requires the full network suite to pass on the WASM
|
||||
worker and performance numbers to be published. The production worker is not in
|
||||
this branch yet, so this spike builds the Phase 5 harness and records the native
|
||||
baseline that the WASM worker must match.
|
||||
|
||||
Run on `k-uce`:
|
||||
|
||||
```bash
|
||||
bash spikes/wasm-phase5/run_phase5.sh
|
||||
```
|
||||
|
||||
Expected final line:
|
||||
|
||||
```text
|
||||
PHASE5 HARNESS: PASS
|
||||
```
|
||||
|
||||
Artifacts are written under `/tmp/uce/wasm-phase5/`:
|
||||
|
||||
- `native-network.json` — full native network suite result.
|
||||
- `native-starter.json` — starter-focused parity subset; this protects against
|
||||
a vacuous `--match starter` exit gate.
|
||||
- `site-static-audit.{json,md}` — candidate cross-request/static-state risks in
|
||||
`site/` for the §3.2 semantic change audit.
|
||||
- `benchmark.{json,md}` — warmed native baseline for the three Phase 5 budget
|
||||
pages:
|
||||
- `template-heavy-doc`: `/doc/singlepage.uce`
|
||||
- `sqlite-page`: `/demo/sqlite.uce`
|
||||
- `component-heavy-starter`: `/examples/uce-starter/?dashboard`
|
||||
|
||||
`benchmark.py` also accepts `--wasm-base-url` once a WASM worker endpoint exists.
|
||||
When provided, it compares WASM medians against the Phase 5 budget of ≤2× native
|
||||
page latency. Workspace birth and internal component call overhead budgets still
|
||||
need worker-internal probes; this harness documents the gap rather than faking
|
||||
those numbers.
|
||||
|
||||
Current scope:
|
||||
|
||||
- Native parity and baseline collection are automated.
|
||||
- WASM parity/performance comparison is ready but blocked on the production WASM
|
||||
worker endpoint.
|
||||
- The static-state audit is heuristic and intentionally conservative; findings
|
||||
must be reviewed by a human before migration work is scheduled.
|
||||
Executable
+100
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Phase 5 audit for cross-request/static state risks in site files."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from dataclasses import asdict, dataclass
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
SITE = ROOT / "site"
|
||||
|
||||
PATTERNS = [
|
||||
("static local/global", "static "),
|
||||
("once hook", "ONCE("),
|
||||
("init hook", "INIT("),
|
||||
("background task", "task_"),
|
||||
]
|
||||
|
||||
SKIP_PARTS = {".git", "tmp", "work", "bin", "pkg", "__pycache__"}
|
||||
|
||||
|
||||
@dataclass
|
||||
class Finding:
|
||||
path: str
|
||||
line: int
|
||||
kind: str
|
||||
text: str
|
||||
note: str
|
||||
|
||||
|
||||
def iter_files() -> list[Path]:
|
||||
result: list[Path] = []
|
||||
for path in SITE.rglob("*"):
|
||||
if not path.is_file():
|
||||
continue
|
||||
if any(part in SKIP_PARTS for part in path.parts):
|
||||
continue
|
||||
if path.suffix not in {".uce", ".h", ".txt"}:
|
||||
continue
|
||||
result.append(path)
|
||||
return sorted(result)
|
||||
|
||||
|
||||
def note_for(kind: str, text: str) -> str:
|
||||
if kind in {"once hook", "init hook"}:
|
||||
return "Audit behavior under per-request wasm workspaces; ONCE/INIT may need host-side cache semantics if used for cross-request state."
|
||||
if kind == "background task":
|
||||
return "Task APIs cross request lifetimes; verify they are host handles, not guest statics."
|
||||
return "Check whether state is request-local, immutable, or intentionally persistent; unit statics reset per wasm workspace."
|
||||
|
||||
|
||||
def scan() -> list[Finding]:
|
||||
findings: list[Finding] = []
|
||||
for path in iter_files():
|
||||
try:
|
||||
lines = path.read_text(encoding="utf-8").splitlines()
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
for lineno, line in enumerate(lines, 1):
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("//") or stripped.startswith("# "):
|
||||
continue
|
||||
for kind, needle in PATTERNS:
|
||||
if needle in line:
|
||||
findings.append(Finding(
|
||||
path=str(path.relative_to(ROOT)),
|
||||
line=lineno,
|
||||
kind=kind,
|
||||
text=stripped[:180],
|
||||
note=note_for(kind, stripped),
|
||||
))
|
||||
return findings
|
||||
|
||||
|
||||
def write_reports(findings: list[Finding], out_dir: Path) -> None:
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
(out_dir / "site-static-audit.json").write_text(json.dumps([asdict(f) for f in findings], indent=2) + "\n")
|
||||
lines = ["# Phase 5 site static-state audit", ""]
|
||||
if not findings:
|
||||
lines.append("No candidate cross-request/static-state patterns found.")
|
||||
else:
|
||||
lines.extend(["| file | line | kind | code | note |", "|---|---:|---|---|---|"])
|
||||
for f in findings:
|
||||
code = f.text.replace("|", "\\|")
|
||||
note = f.note.replace("|", "\\|")
|
||||
lines.append(f"| {f.path} | {f.line} | {f.kind} | `{code}` | {note} |")
|
||||
(out_dir / "site-static-audit.md").write_text("\n".join(lines) + "\n")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
out_dir = Path("/tmp/uce/wasm-phase5")
|
||||
findings = scan()
|
||||
write_reports(findings, out_dir)
|
||||
print(f"Found {len(findings)} candidate static/cross-request patterns")
|
||||
print(f"wrote {out_dir / 'site-static-audit.json'} and {out_dir / 'site-static-audit.md'}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Executable
+153
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Phase 5 native/WASM benchmark harness.
|
||||
|
||||
This runner intentionally has no third-party dependencies. It records a warmed
|
||||
native HTTP baseline now and can compare against a future wasm worker by passing
|
||||
--wasm-base-url. Results are written as JSON plus a small Markdown table.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import http.client
|
||||
import json
|
||||
import statistics
|
||||
import time
|
||||
from dataclasses import asdict, dataclass
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
||||
@dataclass
|
||||
class Target:
|
||||
name: str
|
||||
url: str
|
||||
expected: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class Measurement:
|
||||
backend: str
|
||||
target: str
|
||||
url: str
|
||||
ok: bool
|
||||
status: int
|
||||
samples_ms: list[float]
|
||||
median_ms: float
|
||||
mean_ms: float
|
||||
min_ms: float
|
||||
max_ms: float
|
||||
note: str = ""
|
||||
|
||||
|
||||
def request_once(base_url: str, host_header: str, path: str, timeout: float) -> tuple[int, bytes, float]:
|
||||
parsed = urlparse(base_url)
|
||||
if parsed.scheme not in ("http", "https") or not parsed.hostname:
|
||||
raise SystemExit(f"invalid base url: {base_url}")
|
||||
port = parsed.port or (443 if parsed.scheme == "https" else 80)
|
||||
conn_cls = http.client.HTTPSConnection if parsed.scheme == "https" else http.client.HTTPConnection
|
||||
headers = {}
|
||||
if host_header:
|
||||
headers["Host"] = host_header
|
||||
started = time.perf_counter()
|
||||
conn = conn_cls(parsed.hostname, port, timeout=timeout)
|
||||
try:
|
||||
conn.request("GET", path, headers=headers)
|
||||
response = conn.getresponse()
|
||||
body = response.read()
|
||||
elapsed_ms = (time.perf_counter() - started) * 1000.0
|
||||
return response.status, body, elapsed_ms
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def measure_backend(backend: str, base_url: str, host_header: str, targets: list[Target], warmups: int, samples: int, timeout: float) -> list[Measurement]:
|
||||
results: list[Measurement] = []
|
||||
for target in targets:
|
||||
path = urlparse(target.url).path
|
||||
if "?" in target.url:
|
||||
path = target.url
|
||||
for _ in range(warmups):
|
||||
request_once(base_url, host_header, target.url, timeout)
|
||||
durations: list[float] = []
|
||||
status = 0
|
||||
ok = True
|
||||
note = ""
|
||||
for _ in range(samples):
|
||||
status, body, elapsed_ms = request_once(base_url, host_header, target.url, timeout)
|
||||
durations.append(elapsed_ms)
|
||||
text = body.decode("utf-8", errors="replace")
|
||||
if status != 200:
|
||||
ok = False
|
||||
note = f"HTTP {status}"
|
||||
elif target.expected and target.expected not in text:
|
||||
ok = False
|
||||
note = f"missing marker {target.expected!r}"
|
||||
results.append(Measurement(
|
||||
backend=backend,
|
||||
target=target.name,
|
||||
url=target.url,
|
||||
ok=ok,
|
||||
status=status,
|
||||
samples_ms=durations,
|
||||
median_ms=statistics.median(durations),
|
||||
mean_ms=statistics.fmean(durations),
|
||||
min_ms=min(durations),
|
||||
max_ms=max(durations),
|
||||
note=note,
|
||||
))
|
||||
return results
|
||||
|
||||
|
||||
def compare(results: list[Measurement]) -> list[str]:
|
||||
native = {r.target: r for r in results if r.backend == "native"}
|
||||
lines = ["| target | backend | median ms | mean ms | budget | status |", "|---|---:|---:|---:|---|---|"]
|
||||
for result in results:
|
||||
budget = "baseline"
|
||||
status = "PASS" if result.ok else "FAIL"
|
||||
if result.backend != "native" and result.target in native:
|
||||
limit = native[result.target].median_ms * 2.0
|
||||
budget = f"≤ {limit:.1f} ms"
|
||||
if result.median_ms > limit:
|
||||
status = "FAIL"
|
||||
lines.append(f"| {result.target} | {result.backend} | {result.median_ms:.1f} | {result.mean_ms:.1f} | {budget} | {status} {result.note} |")
|
||||
return lines
|
||||
|
||||
|
||||
def build_targets() -> list[Target]:
|
||||
return [
|
||||
Target("template-heavy-doc", "/doc/singlepage.uce", "UCE API"),
|
||||
Target("sqlite-page", "/demo/sqlite.uce", "SQLite"),
|
||||
Target("component-heavy-starter", "/examples/uce-starter/?dashboard", "Dashboard"),
|
||||
]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Phase 5 native/wasm benchmark harness")
|
||||
parser.add_argument("--native-base-url", default="http://localhost:80")
|
||||
parser.add_argument("--wasm-base-url", default="", help="optional wasm worker URL; omitted until worker exists")
|
||||
parser.add_argument("--host-header", default="uce.openfu.com")
|
||||
parser.add_argument("--warmups", type=int, default=2)
|
||||
parser.add_argument("--samples", type=int, default=5)
|
||||
parser.add_argument("--timeout", type=float, default=10.0)
|
||||
parser.add_argument("--out-dir", default="/tmp/uce/wasm-phase5")
|
||||
args = parser.parse_args()
|
||||
|
||||
targets = build_targets()
|
||||
results = measure_backend("native", args.native_base_url, args.host_header, targets, args.warmups, args.samples, args.timeout)
|
||||
if args.wasm_base_url:
|
||||
results.extend(measure_backend("wasm", args.wasm_base_url, args.host_header, targets, args.warmups, args.samples, args.timeout))
|
||||
|
||||
out_dir = Path(args.out_dir)
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
(out_dir / "benchmark.json").write_text(json.dumps([asdict(r) for r in results], indent=2) + "\n")
|
||||
md_lines = ["# Phase 5 benchmark report", "", *compare(results), ""]
|
||||
if not args.wasm_base_url:
|
||||
md_lines.append("WASM worker URL was not provided; this report is the native baseline that future wasm runs compare against.")
|
||||
(out_dir / "benchmark.md").write_text("\n".join(md_lines) + "\n")
|
||||
print("\n".join(md_lines))
|
||||
print(f"wrote {out_dir / 'benchmark.json'} and {out_dir / 'benchmark.md'}")
|
||||
return 0 if all(r.ok for r in results) else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
# Phase 5 harness: native parity, site static audit, and native perf baseline.
|
||||
# Run on k-uce from repo root.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/../.."
|
||||
|
||||
OUT=/tmp/uce/wasm-phase5
|
||||
mkdir -p "$OUT"
|
||||
|
||||
python3 tests/run_network_tests.py --include-internal --json-report "$OUT/native-network.json"
|
||||
python3 tests/run_network_tests.py --include-internal --match starter --json-report "$OUT/native-starter.json"
|
||||
python3 spikes/wasm-phase5/audit_site_statics.py
|
||||
python3 spikes/wasm-phase5/benchmark.py --out-dir "$OUT"
|
||||
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
from pathlib import Path
|
||||
out = Path('/tmp/uce/wasm-phase5')
|
||||
network = json.loads((out / 'native-network.json').read_text())
|
||||
starter = json.loads((out / 'native-starter.json').read_text())
|
||||
bench = json.loads((out / 'benchmark.json').read_text())
|
||||
failures = [r for r in network if not r['ok']] + [r for r in starter if not r['ok']] + [r for r in bench if not r['ok']]
|
||||
if failures:
|
||||
print('PHASE5 HARNESS: FAIL')
|
||||
for failure in failures:
|
||||
print(failure)
|
||||
raise SystemExit(1)
|
||||
print('PHASE5 HARNESS: PASS')
|
||||
print(f"network_cases={len(network)} starter_cases={len(starter)} benchmarks={len(bench)}")
|
||||
print(f"reports={out}")
|
||||
PY
|
||||
Reference in New Issue
Block a user