mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-09-13 02:18:31 +00:00
perf(harness): close page on error + MD040 fence + CHANGELOG (CodeRabbit)
- frameTimeOnce now closes its page in a finally, so a failing run doesn't leak the page until the final browser.close() (CodeRabbit). - fenced code block gets a bash language hint (MD040). - CHANGELOG mentions the new --song frame-time mode.
This commit is contained in:
@@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
next root-level module can't ship broken.
|
next root-level module can't ship broken.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- **Perf harness now measures 2D-highway frame time (R3c gate).** `scripts/perf-baseline.mjs` gains a `--song` mode that reports per-frame draw-cost p50/p95/p99 (draw-tagged via `highway.addDrawHook`), the metric that gates the `highway.js` split. Maintainer/CI-only; baseline recorded in `docs/perf-baseline.md`.
|
||||||
- **`routers/` — extracting `server.py`'s route layer, cheapest-first (R3).** Each PR moves a cohesive route group into a `fastapi.APIRouter` under `lib/routers/`, mounted with `app.include_router(...)` at its original site (FastAPI matches in registration order; the full route table stays byte-identical). Bodies are verbatim — only the decorator receiver (`@app` → `@router`) and singleton reads (`meta_db` → `appstate.meta_db`, resolved at call time) change. So far: `audio_effects` (5), `artist_aliases` (5), `loops` (3), `playlists` (12 + covers), `ws_highway` (the 902-line highway chart WebSocket), `chart` (split/unsplit/work/fileinfo — unblocked by the DLC-path substrate). The DLC library-path resolution (`_get_dlc_dir`, pure `_resolve_dlc_path`) moved to `lib/dlc_paths.py`, reading paths through the seam; `config_dir`/`dlc_dir`/`dlc_dir_env` now ride the `appstate` seam (env-derived, so the pop-and-reimport fixtures reconfigure it for free), and the shared request-field sanitizer `_clean_str` moved to `lib/reqfields.py`. The next cut is picked by a dependency-closure scan that ranks groups by how many `monkeypatch.setattr(server, …)` targets they'd drag along.
|
- **`routers/` — extracting `server.py`'s route layer, cheapest-first (R3).** Each PR moves a cohesive route group into a `fastapi.APIRouter` under `lib/routers/`, mounted with `app.include_router(...)` at its original site (FastAPI matches in registration order; the full route table stays byte-identical). Bodies are verbatim — only the decorator receiver (`@app` → `@router`) and singleton reads (`meta_db` → `appstate.meta_db`, resolved at call time) change. So far: `audio_effects` (5), `artist_aliases` (5), `loops` (3), `playlists` (12 + covers), `ws_highway` (the 902-line highway chart WebSocket), `chart` (split/unsplit/work/fileinfo — unblocked by the DLC-path substrate). The DLC library-path resolution (`_get_dlc_dir`, pure `_resolve_dlc_path`) moved to `lib/dlc_paths.py`, reading paths through the seam; `config_dir`/`dlc_dir`/`dlc_dir_env` now ride the `appstate` seam (env-derived, so the pop-and-reimport fixtures reconfigure it for free), and the shared request-field sanitizer `_clean_str` moved to `lib/reqfields.py`. The next cut is picked by a dependency-closure scan that ranks groups by how many `monkeypatch.setattr(server, …)` targets they'd drag along.
|
||||||
- **`routers/` — the first extracted route module (R3).** The five audio-effects mapping
|
- **`routers/` — the first extracted route module (R3).** The five audio-effects mapping
|
||||||
endpoints move out of `server.py` into `lib/routers/audio_effects.py` as a
|
endpoints move out of `server.py` into `lib/routers/audio_effects.py` as a
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ highway actually painted count (the other ~half are cheap no-op loops that would
|
|||||||
otherwise mask a regression). Any `highway.js` change must re-run this on the
|
otherwise mask a regression). Any `highway.js` change must re-run this on the
|
||||||
same machine and stay within noise of these numbers.
|
same machine and stay within noise of these numbers.
|
||||||
|
|
||||||
```
|
```bash
|
||||||
node scripts/perf-baseline.mjs --base http://127.0.0.1:8300 \
|
node scripts/perf-baseline.mjs --base http://127.0.0.1:8300 \
|
||||||
--song "Arcturus - The Sham Mirrors - Kinetic.feedpak"
|
--song "Arcturus - The Sham Mirrors - Kinetic.feedpak"
|
||||||
```
|
```
|
||||||
|
|||||||
+33
-27
@@ -85,35 +85,41 @@ async function clientMetrics() {
|
|||||||
// ── 2D highway per-frame draw cost (needs --song + a seeded library) ──────────
|
// ── 2D highway per-frame draw cost (needs --song + a seeded library) ──────────
|
||||||
async function frameTimeOnce(browser) {
|
async function frameTimeOnce(browser) {
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
// Wrap rAF before any page script; mark frames the highway actually drew.
|
let f, t2, notes;
|
||||||
await page.addInitScript(() => {
|
try {
|
||||||
window.__f = [];
|
// Wrap rAF before any page script; mark frames the highway actually drew.
|
||||||
window.__drew = false;
|
await page.addInitScript(() => {
|
||||||
const raf = window.requestAnimationFrame.bind(window);
|
window.__f = [];
|
||||||
window.requestAnimationFrame = (cb) => raf((t) => {
|
|
||||||
window.__drew = false;
|
window.__drew = false;
|
||||||
const t0 = performance.now();
|
const raf = window.requestAnimationFrame.bind(window);
|
||||||
try { cb(t); } finally { window.__f.push({ ms: performance.now() - t0, drew: window.__drew }); }
|
window.requestAnimationFrame = (cb) => raf((t) => {
|
||||||
|
window.__drew = false;
|
||||||
|
const t0 = performance.now();
|
||||||
|
try { cb(t); } finally { window.__f.push({ ms: performance.now() - t0, drew: window.__drew }); }
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
await page.goto(BASE, { waitUntil: 'networkidle', timeout: 60000 });
|
||||||
await page.goto(BASE, { waitUntil: 'networkidle', timeout: 60000 });
|
await page.evaluate(() => document.getElementById('v3-onboarding')?.remove());
|
||||||
await page.evaluate(() => document.getElementById('v3-onboarding')?.remove());
|
await page.evaluate((s) => window.playSong(s), SONG);
|
||||||
await page.evaluate((s) => window.playSong(s), SONG);
|
await page.waitForFunction(() => (window.highway?.getNotes?.() || []).length >= 0 && window.highway?.getSongInfo?.(),
|
||||||
await page.waitForFunction(() => (window.highway?.getNotes?.() || []).length >= 0 && window.highway?.getSongInfo?.(),
|
null, { timeout: 45000 }).catch(() => {});
|
||||||
null, { timeout: 45000 }).catch(() => {});
|
await page.waitForFunction(() => (window.highway?.getNotes?.() || []).length > 0, null, { timeout: 45000 });
|
||||||
await page.waitForFunction(() => (window.highway?.getNotes?.() || []).length > 0, null, { timeout: 45000 });
|
await page.evaluate(() => window.highway.addDrawHook(() => { window.__drew = true; }));
|
||||||
await page.evaluate(() => window.highway.addDrawHook(() => { window.__drew = true; }));
|
// Start playback so draw() leaves its paused-throttle path; confirm the clock advances.
|
||||||
// Start playback so draw() leaves its paused-throttle path; confirm the clock advances.
|
await page.evaluate(async () => { const a = window.highway.getAudioElement?.(); if (a) a.muted = true; await a?.play?.(); });
|
||||||
await page.evaluate(async () => { const a = window.highway.getAudioElement?.(); if (a) a.muted = true; await a?.play?.(); });
|
await page.waitForTimeout(1500);
|
||||||
await page.waitForTimeout(1500);
|
const t1 = await page.evaluate(() => window.highway.getTime());
|
||||||
const t1 = await page.evaluate(() => window.highway.getTime());
|
await page.evaluate(() => { window.__f.length = 0; });
|
||||||
await page.evaluate(() => { window.__f.length = 0; });
|
await page.waitForTimeout(FRAME_S * 1000);
|
||||||
await page.waitForTimeout(FRAME_S * 1000);
|
({ f, t2, notes } = await page.evaluate(() => ({
|
||||||
const { f, t2, notes } = await page.evaluate(() => ({
|
f: window.__f.slice(), t2: window.highway.getTime(), notes: window.highway.getNotes().length,
|
||||||
f: window.__f.slice(), t2: window.highway.getTime(), notes: window.highway.getNotes().length,
|
})));
|
||||||
}));
|
if (!(t2 > t1 + 1)) throw new Error(`clock did not advance (${t1}→${t2}) — measured the paused path`);
|
||||||
await page.close();
|
} finally {
|
||||||
if (!(t2 > t1 + 1)) throw new Error(`clock did not advance (${t1}→${t2}) — measured the paused path`);
|
// Always close, even when an await above throws — otherwise a failing
|
||||||
|
// run leaks its page until the final browser.close().
|
||||||
|
await page.close();
|
||||||
|
}
|
||||||
const drew = f.filter((x) => x.drew).map((x) => x.ms);
|
const drew = f.filter((x) => x.drew).map((x) => x.ms);
|
||||||
return { drew, total: f.length, notes };
|
return { drew, total: f.length, notes };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user