144 lines
6.5 KiB
Markdown
144 lines
6.5 KiB
Markdown
# Performance Baseline Harness (`perf/`)
|
||
|
||
Automated performance baseline and regression harness for the **ytplayer PWA**.
|
||
|
||
The harness drives the real Bun backend server (`server/server.js`) serving static Brotli-compressed assets, combined with Playwright browser automation on Chromium and WebKit.
|
||
|
||
---
|
||
|
||
## 1. Architecture
|
||
|
||
### Backend Server (`server/server.js`)
|
||
- Runs as an isolated child process using **Bun** on an ephemeral port.
|
||
- Configured with dedicated scratch directories under `perf/.tmp/` (`DATA_DIR`, `DB_PATH`, `MEDIA_DIR`) so no production or developer data is ever touched.
|
||
- Employs the real static asset delivery stack (`Brotli` encoding, `ETag` generation, `Cache-Control` immutable caching for version-stamped assets, and `computeBuildTag()`).
|
||
|
||
### Browser Automation & Network Throttling
|
||
- **Chromium**:
|
||
- Network emulation via Chrome DevTools Protocol (`Network.emulateNetworkConditions`).
|
||
- CPU throttling at **4× slowdown** (`Emulation.setCPUThrottlingRate`).
|
||
- Unrestricted audio/video playback via `--autoplay-policy=no-user-gesture-required`.
|
||
- **WebKit**:
|
||
- WebKit on Linux lacks CDP network emulation capabilities.
|
||
- Throttling and stalling are handled by a dedicated local proxy (`perf/proxy.mjs`) sitting between WebKit and the Bun server.
|
||
- The proxy shapes traffic by pacing chunk delivery through backpressured streams and injecting round-trip latency and stalls.
|
||
- **Network Profiles**:
|
||
- **`lte`**: 1.6 Mbps (200 KB/s) download, 750 kbps upload, 150 ms round-trip latency.
|
||
- **`lossy`**: 1.0 Mbps (125 KB/s) download, 250 ms latency, with intermittent connection stalls (every 7th request delayed by 1,500 ms).
|
||
|
||
### Shell Fixture Extractor (`perf/make-shell-fixture.sh`)
|
||
- Archives the frontend from git commits (`git archive <commit> frontend`) into `perf/fixtures/shell-<tag>/`.
|
||
- Computes the build tag using the exact sha256 walk algorithm from `server/server.js computeBuildTag()`.
|
||
- Validates the computed tag against live production (`https://worship.hesed.sbs/api/version`).
|
||
|
||
---
|
||
|
||
## 2. Test Scenarios
|
||
|
||
1. **`cold` (Cold First Load)**:
|
||
- Fresh browser context with empty cache, no service worker, and clean storage.
|
||
- Measures initial page fetch, FCP, LCP, DOMContentLoaded, boot milestone (`#cards` render), media playback readiness, service worker precaching duration, and origin storage quota.
|
||
2. **`warm` (Warm Reload under SW)**:
|
||
- Re-navigates the page with active Service Worker and primed HTTP cache.
|
||
- Measures cached asset retrieval efficiency and second-load boot performance.
|
||
3. **`offline` (Offline Reload)**:
|
||
- Sets offline state and reloads the application.
|
||
- Verifies the app boots entirely from `CacheStorage` precached shell assets without network connectivity.
|
||
4. **`update-js` (Update N → N+1 with 1-line JS comment)**:
|
||
- Boots client against Tree N (base frontend), establishing an active Service Worker.
|
||
- Swaps server to Tree N+1 (one comment line appended to `app.js`, altering `BUILD_TAG`).
|
||
- Triggers `registration.update()` and measures total wire bytes and time until the new Service Worker reaches `waiting` state (`installed`).
|
||
5. **`update-css` (Update N → N+1 with 1-line CSS rule)**:
|
||
- Identical to `update-js`, but Tree N+1 has one CSS rule appended to `theme-glass.css`.
|
||
- Measures the full shell re-download penalty caused by any stylesheet modification.
|
||
|
||
---
|
||
|
||
## 3. Metrics Collected
|
||
|
||
Each scenario records the following metrics across **5 independent runs** (reporting min, max, and median):
|
||
- **Request Count**: Total HTTP requests made across the wire.
|
||
- **Wire Bytes**: Total compressed bytes transferred over the network.
|
||
- **Wire Bytes by Type**: Categorized byte breakdown (`html`, `js`, `css`, `fonts`, `images`, `media`, `api`, `other`).
|
||
- **FCP (First Contentful Paint)**: Time from navigation start to first rendered content (ms).
|
||
- **LCP (Largest Contentful Paint)**: Time to largest contentful paint via `PerformanceObserver` (ms).
|
||
- **DOMContentLoaded**: Time to DOMContentLoaded event end (ms).
|
||
- **Boot Done**: Time from navigation start until the first `#cards` view rendering is completed (ms).
|
||
- **Long Tasks Total**: Cumulative execution time of main thread tasks exceeding 50 ms (ms, Chromium).
|
||
- **SW Install Duration**: Time from Service Worker registration until precaching completes (ms).
|
||
- **SW Install Wire Bytes**: Network bytes transmitted during Service Worker installation.
|
||
- **Storage Estimate**: Quota and usage reported by `navigator.storage.estimate()` (bytes).
|
||
- **Media Ready**: Elapsed time for deterministic media fixture (`benchmedia01`) to reach `readyState >= 3` / playing state. Notes if autoplay policy blocked playback.
|
||
|
||
---
|
||
|
||
## 4. How to Run
|
||
|
||
### Prerequisites
|
||
Symlink dependencies and ensure server dependencies are installed:
|
||
```bash
|
||
# Worktree root
|
||
ln -s ~/development/personal/ytplayer/node_modules node_modules
|
||
cd server && bun install && cd ..
|
||
```
|
||
|
||
### Basic Commands
|
||
Run the complete baseline suite (5 runs, all browsers, LTE profile):
|
||
```bash
|
||
node perf/baseline.mjs
|
||
```
|
||
|
||
Run a specific browser or profile:
|
||
```bash
|
||
# Chromium on LTE profile
|
||
node perf/baseline.mjs --browser chromium --profile lte
|
||
|
||
# WebKit on LTE profile
|
||
node perf/baseline.mjs --browser webkit --profile lte
|
||
|
||
# Both browsers on lossy profile
|
||
node perf/baseline.mjs --browser all --profile lossy
|
||
```
|
||
|
||
Run a single scenario:
|
||
```bash
|
||
# Cold load only
|
||
node perf/baseline.mjs --scenario cold
|
||
|
||
# Update scenario with 1-line JS change
|
||
node perf/baseline.mjs --scenario update-js
|
||
|
||
# Update scenario with 1-line CSS change
|
||
node perf/baseline.mjs --scenario update-css
|
||
```
|
||
|
||
Configure run count:
|
||
```bash
|
||
# Quick sanity check (1 iteration)
|
||
node perf/baseline.mjs --runs 1
|
||
|
||
# High precision benchmark (7 iterations)
|
||
node perf/baseline.mjs --runs 7
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Phase 5 Comparison Mode (`--compare`)
|
||
|
||
To verify performance gains and detect regressions in later phases:
|
||
```bash
|
||
node perf/baseline.mjs --compare perf/results/baseline-2026-10-07.json
|
||
```
|
||
Prints an ASCII/Markdown delta comparison table highlighting percentage changes across wire bytes, boot duration, and update overhead.
|
||
|
||
---
|
||
|
||
## 6. Hidden Timers Verification (`perf/hidden-timers.mjs`)
|
||
|
||
Verifies that non-playback UI intervals (`chips`, `reanchor`, `rotateChips`, `flush`, `checkBuildTag`) suspend while the document is hidden and restore upon visibility, while essential background intervals (`maybeCheck`, 120s resume check) remain active:
|
||
```bash
|
||
node perf/hidden-timers.mjs
|
||
```
|
||
Exits with code 0 on success, or code 1 if any interval fails to pause, fails to resume, or if page errors occur.
|
||
|