Run the check browser on the GPU instead of rasterising in software
The harness forced software WebGL (--disable-gpu --use-gl=swiftshader) on every host. Measured here on an idle editor page over 20 s: that costs 373% of a core at 8 fps, while the Intel HD 630 via ANGLE/Vulkan costs 69% at 56 fps. The full suite now runs in ~124 s. Note the app is not rendering 60 fps as previously assumed - in software it only reaches 8, and a single frame costs more than any throttle interval would save, which is why capping the framerate had measured no effect. --use-angle=gl and --use-gl=egl look cheaper still but get no WebGL context at all, so the app never renders. They are not usable and are named in the comment as such. There is deliberately no software fallback: a host without a usable GPU should fail loudly rather than drag the machine to a standstill. None of the 267 checks reads the rendered image anyway - they read window.__gm.locate() and the debug hook, and the screenshots assert nothing - but Pixi needs WebGL2 to run at all. The browser launch moves out of harness.mjs into tools/lib/browser.mjs, which also supports GM_REMOTE=user@host via an SSH-forwarded CDP port. That path is unfinished: the tunnel degraded until the suite crawled at ~25 s per check, and forcing software WebGL on a desktop with a real card brought it to its knees. README says so plainly rather than advertising it. Also: the jump-run HUD check asserted a fixed "Coins 1/10", which fails on a faster host where the same walk sweeps up two coins. It now asserts the HUD against the count the run actually reached - the real guarantee is that HUD and state agree. Verified: npm run check green (404 tests), 267 e2e ALL PASSED and 24 jump-run checks ALL PASSED against the GPU flags. The fallback removal leaves the flag set on this host identical (same flags, different order), so it was not re-run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3d038e11cb
commit
32a40ee34b
5 files changed
+180
-29
No files matched your search
+8
-28
@@ -7,19 +7,19 @@
|
||||
*
|
||||
* await runChecks({ projectId, baseUrl, port, shotPrefix }, async (t) => { ... });
|
||||
*
|
||||
* The harness always waits for chromium to actually exit (a SwiftShader GPU process kept
|
||||
* rendering at full CPU once when only SIGTERM was sent) and exits with code 1 on failures.
|
||||
* The harness always waits for chromium to actually exit (a leftover GPU process kept rendering at
|
||||
* full CPU once when only SIGTERM was sent) and exits with code 1 on failures.
|
||||
*
|
||||
* A run that ends without printing its summary exits 1 as well. Every wait here can time out for
|
||||
* that reason: chromium dies under memory pressure on this machine, and a run that stops halfway
|
||||
* used to exit 0 – silently reporting success for checks it never ran.
|
||||
*/
|
||||
import { spawn } from 'node:child_process';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import { launchBrowser } from './browser.mjs';
|
||||
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
/** Generous: navigation and screenshots under swiftshader are slow, a wedged chromium is forever. */
|
||||
/** Generous on purpose: a slow navigation still finishes, a wedged chromium never does. */
|
||||
const CDP_TIMEOUT_MS = 30_000;
|
||||
const SERVER_TIMEOUT_MS = 20_000;
|
||||
|
||||
@@ -315,28 +315,8 @@ function makeTools({ cdp, projectId, baseUrl, consoleLog, check, shotPrefix }) {
|
||||
|
||||
export async function runChecks(options, body) {
|
||||
const { projectId, baseUrl, port = 9333, shotPrefix = 'gm-e2e', width = 1280, height = 800 } = options;
|
||||
const chrome = spawn(
|
||||
'chromium',
|
||||
[
|
||||
'--headless',
|
||||
'--no-sandbox',
|
||||
'--disable-gpu',
|
||||
'--use-gl=swiftshader',
|
||||
'--enable-unsafe-swiftshader',
|
||||
`--remote-debugging-port=${port}`,
|
||||
`--window-size=${width},${height}`,
|
||||
'--hide-scrollbars',
|
||||
// Memory on this machine is tight and a long run is where chromium dies. None of this
|
||||
// serves a headless check: the stderr was full of GCM sign-in retries before.
|
||||
'--disable-background-networking',
|
||||
'--disable-sync',
|
||||
'--disable-extensions',
|
||||
'--no-first-run',
|
||||
'--mute-audio',
|
||||
'about:blank',
|
||||
],
|
||||
{ stdio: ['ignore', 'ignore', 'pipe'] },
|
||||
);
|
||||
const { proc: chrome, stop: stopBrowser, where } = launchBrowser({ port, width, height });
|
||||
console.log(`browser: ${where}`);
|
||||
|
||||
const consoleLog = [];
|
||||
let failures = 0;
|
||||
@@ -418,12 +398,12 @@ export async function runChecks(options, body) {
|
||||
} finally {
|
||||
await new Promise((resolve) => {
|
||||
if (chrome.exitCode !== null) return resolve();
|
||||
const timer = setTimeout(() => chrome.kill('SIGKILL'), 5000);
|
||||
const timer = setTimeout(() => chrome.kill('SIGKILL'), 15_000);
|
||||
chrome.once('exit', () => {
|
||||
clearTimeout(timer);
|
||||
resolve();
|
||||
});
|
||||
chrome.kill('SIGTERM');
|
||||
stopBrowser();
|
||||
});
|
||||
}
|
||||
if (consoleLog.length) console.log('--- console ---\n' + consoleLog.slice(0, 20).join('\n'));
|
||||
|
||||
Reference in new issue
Block a user