113 lines
7.2 KiB
JavaScript
113 lines
7.2 KiB
JavaScript
// Store install while connected to a (mock) node, on either engine: --nt forces the
|
|
// no-threads build. Prints the [STORE]/[companion] log and whether the app landed.
|
|
import { createRequire } from 'node:module';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
const require = createRequire(path.join(here, '..', 'package.json'));
|
|
const pw = require('playwright');
|
|
const arg = (n, d) => { const i = process.argv.indexOf(n); return i > 0 ? process.argv[i + 1] : d; };
|
|
const URL_ = arg('--url', 'http://localhost:8188/'), WS = arg('--ws', 'localhost:18766');
|
|
const NT = process.argv.includes('--nt'), ENGINE = arg('--browser', 'chromium');
|
|
const W = 800, H = 480, sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
const browser = await pw[ENGINE].launch();
|
|
const ctx = await browser.newContext({ viewport: { width: 1100, height: 760 } });
|
|
const page = await ctx.newPage();
|
|
const log = [];
|
|
page.on('console', (m) => log.push(`${(performance.now() / 1000).toFixed(1)} ${m.type()} ${m.text()}`));
|
|
page.on('pageerror', (e) => log.push('PAGEERROR ' + e));
|
|
async function tap(x, y) {
|
|
const r = await page.locator('#screen').boundingBox();
|
|
await page.mouse.move(r.x + (x + 0.5) * r.width / W, r.y + (y + 0.5) * r.height / H);
|
|
await page.mouse.down(); await sleep(80); await page.mouse.up(); await sleep(150);
|
|
}
|
|
const booted = () => page.waitForFunction(() => window.wadamesh && window.wadamesh.running(), null, { timeout: 60000 });
|
|
await page.goto(URL_ + (NT ? '?nt' : ''), { waitUntil: 'load' });
|
|
await booted(); await sleep(4000);
|
|
await tap(60, 283); await sleep(2000);
|
|
await page.click('#btn-connect');
|
|
await page.fill('#ws-host', WS);
|
|
await page.click('#connect-dlg [data-kind="ws"]');
|
|
await page.waitForLoadState('load'); await booted(); await sleep(6000);
|
|
console.log('badge:', await page.locator('#mode').textContent(), 'bundle nt:', await page.evaluate(() => [...document.scripts].some((s) => /nt\/wadamesh/.test(s.src))));
|
|
await tap(695, 360); await sleep(1500); await tap(60, 150); await sleep(5000);
|
|
const TRACE_CPU = process.argv.includes('--cpu');
|
|
if (TRACE_CPU) await browser.startTracing(page, { categories: ['disabled-by-default-v8.cpu_profiler', 'v8'] });
|
|
const APP = arg('--app', '');
|
|
if (APP) {
|
|
for (let i = 0; i < 12; i++) { // scroll the list until the app's row shows
|
|
const p = await page.evaluate((t) => window.wadamesh.findText(t), APP);
|
|
if (p && p[1] > 90 && p[1] < 440) { console.log('row', APP, p); await tap(740, p[1] + 10); break; }
|
|
const r = await page.locator('#screen').boundingBox();
|
|
await page.mouse.move(r.x + 400 * r.width / W, r.y + 400 * r.height / H); await page.mouse.down();
|
|
for (let k = 1; k <= 10; k++) { await page.mouse.move(r.x + 400 * r.width / W, r.y + (400 - k * 22) * r.height / H); await sleep(16); }
|
|
await sleep(60); await page.mouse.up(); await sleep(800);
|
|
}
|
|
} else await tap(740, 463);
|
|
if (process.argv.includes('--watch')) {
|
|
for (let k = 0; k < 12; k++) {
|
|
await sleep(2000);
|
|
const r = await Promise.race([page.evaluate(() => JSON.stringify({ f: window.wadamesh.stats.frames, p: window.wadamesh.stats.presents })), sleep(1500).then(() => 'JS BLOCKED')]);
|
|
console.log('t+' + (2 * (k + 1)) + 's', r);
|
|
if (r === 'JS BLOCKED') {
|
|
await sleep(4000);
|
|
if (TRACE_CPU) {
|
|
const buf = await browser.stopTracing();
|
|
const ev = JSON.parse(buf.toString()).traceEvents || [];
|
|
const nodes = new Map(), counts = new Map();
|
|
for (const e of ev) {
|
|
if (e.name !== 'ProfileChunk') continue;
|
|
const d = e.args.data.cpuProfile || {};
|
|
for (const n of d.nodes || []) nodes.set(n.id, n);
|
|
for (const sId of d.samples || []) counts.set(sId, (counts.get(sId) || 0) + 1);
|
|
}
|
|
const byName = new Map();
|
|
for (const [id, c] of counts) { const n = nodes.get(id); const k = n ? (n.callFrame.functionName || '(anon)') + ' ' + (n.callFrame.url || '').split('/').pop() : '?' + id; byName.set(k, (byName.get(k) || 0) + c); }
|
|
console.log('HOT:'); [...byName.entries()].sort((a, b) => b[1] - a[1]).slice(0, 30).forEach(([k, c]) => console.log(' ' + c + ' ' + k));
|
|
// parents of the hottest node
|
|
const parentOf2 = new Map(); for (const m of nodes.values()) for (const c of (m.children || [])) parentOf2.set(c, m.id);
|
|
for (const m of nodes.values()) if (m.parent) parentOf2.set(m.id, m.parent);
|
|
const tops = [...counts.entries()].filter(([id]) => { const m = nodes.get(id); return m && !/idle|program|garbage/.test(m.callFrame.functionName); }).sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
for (const [tid, c] of tops) { const chain = []; let id = tid; for (let k = 0; k < 60 && id; k++) { const m = nodes.get(id); chain.push(m ? m.callFrame.functionName || '(anon)' : '?'); id = parentOf2.get(id); } console.log('STACK ' + c + ': ' + chain.join(' <- ')); }
|
|
const top = null;
|
|
if (top) { const chain = []; let n = nodes.get(top[0]); const parentOf = new Map(); for (const m of nodes.values()) if (m.parent) parentOf.set(m.id, m.parent);
|
|
let id = top[0]; for (let k = 0; k < 40 && id; k++) { const m = nodes.get(id); chain.push(m ? m.callFrame.functionName || '(anon)' : '?'); id = parentOf.get(id); }
|
|
console.log('HOTTEST STACK: ' + chain.join(' <- ')); }
|
|
}
|
|
console.log(log.slice(-12).join('\n')); process.exit(0);
|
|
}
|
|
}
|
|
console.log(log.slice(-15).join('\n'));
|
|
await browser.close(); process.exit(0);
|
|
}
|
|
if (process.argv.includes('--pause')) {
|
|
await sleep(8000);
|
|
const cdp = await ctx.newCDPSession(page);
|
|
await cdp.send('Profiler.enable');
|
|
await cdp.send('Profiler.start');
|
|
await sleep(5000);
|
|
const { profile } = await cdp.send('Profiler.stop');
|
|
const self = new Map();
|
|
const byId = new Map(profile.nodes.map((n) => [n.id, n]));
|
|
const dt = profile.timeDeltas; let i = 0;
|
|
for (const sid of profile.samples) { const n = byId.get(sid); const k = n.callFrame.functionName || '(anon)'; self.set(k, (self.get(k) || 0) + (dt[i++] || 0)); }
|
|
console.log('HOT:\n' + [...self.entries()].sort((a, b) => b[1] - a[1]).slice(0, 25).map(([k, v]) => ' ' + (v / 1000).toFixed(0) + ' ms ' + k).join('\n'));
|
|
console.log(log.slice(-25).join('\n'));
|
|
await cdp.send('Debugger.enable');
|
|
const paused = new Promise((r) => cdp.once('Debugger.paused', r));
|
|
await cdp.send('Debugger.pause');
|
|
const ev = await Promise.race([paused, sleep(10000).then(() => null)]);
|
|
console.log('PAUSED STACK:\n' + (ev ? ev.callFrames.slice(0, 40).map((f) => ' ' + f.functionName + ' @' + f.url.split('/').pop()).join('\n') : '(no pause)'));
|
|
await cdp.send('Debugger.resume').catch(() => {});
|
|
await browser.close(); process.exit(0);
|
|
}
|
|
await page.screenshot({ path: '/tmp/store-before.png' });
|
|
await sleep(15000);
|
|
await page.screenshot({ path: '/tmp/store-after.png' });
|
|
await page.evaluate(() => window.wadamesh.flush());
|
|
const sizes = await page.evaluate(() => { const M = window.wadamesh; try { return JSON.stringify(M.fileSizes ? M.fileSizes() : null); } catch (e) { return String(e); } });
|
|
const hits = await page.evaluate(() => window.wadamesh.findInStorage('wada'));
|
|
console.log('installed:', hits.filter((h) => /apps\//.test(h)));
|
|
console.log(log.filter((l) => /STORE|LUA|companion|error|PAGEERROR|freertos|abort/i.test(l)).slice(-40).join('\n'));
|
|
await browser.close();
|