// Boot tracer — Electron entry that wraps main.js without changing it and // records what happens during the first BOOT_TRACE_MS of a launch. Started by // run.mjs (see there); writes BOOT_TRACE_OUT as JSON. // // Records: slow require()s, main-thread (event-loop) blocks, add-on // activation times, every main-process fetch(), WebContents creation and // first loads, selected log lines, and process count / private memory at // 5 s, 15 s and the end. "use strict"; const T0 = performance.timeOrigin; const now = () => Math.round(performance.now()); const fs = require("fs"), path = require("path"), Module = require("module"); const APP = path.resolve(__dirname, "..", ".."); const OUT = process.env.BOOT_TRACE_OUT; const DURATION = Number(process.env.BOOT_TRACE_MS || 40000); if (!OUT || !process.env.THESEUS_USER_DATA) { console.error("run via scripts/boot-trace/run.mjs"); process.exit(2); } const ev = []; const mark = (kind, label, extra) => ev.push({ t: now(), kind, label, ...extra }); // Slow require()s, top-level only (self time including children). const origLoad = Module._load; let depth = 0; Module._load = function (request) { const s = performance.now(); depth++; try { return origLoad.apply(this, arguments); } finally { depth--; const d = performance.now() - s; if (d > 15 && depth <= 1) mark("require", request, { ms: Math.round(d) }); } }; // Event-loop blocks > 60 ms, sampled every 20 ms. let last = performance.now(); setInterval(() => { const n = performance.now(); const block = n - last - 20; if (block > 60) mark("block", "event loop blocked", { ms: Math.round(block), endedAt: Math.round(n) }); last = n; }, 20).unref(); // Main-process fetch() traffic. const origFetch = globalThis.fetch; globalThis.fetch = async function (input, init) { const url = String((input && input.url) || input); const s = now(); try { const r = await origFetch(input, init); mark("fetch", url.slice(0, 110), { start: s, ms: now() - s, status: r.status }); return r; } catch (e) { mark("fetch", url.slice(0, 110), { start: s, ms: now() - s, error: e?.cause?.code || e?.name }); throw e; } }; for (const k of ["log", "warn", "error"]) { const o = console[k].bind(console); console[k] = (...a) => { const line = a.map(String).join(" "); if (/\[(bns|addons|update|tor)\]|activated|snapshot/i.test(line)) mark("log", line.slice(0, 160)); o(...a); }; } const { app } = require("electron"); app.setPath("userData", process.env.THESEUS_USER_DATA); // With a script as the entry, package.json isn't read: keep the real name and // version so profile paths and version checks behave as in a normal run. const pkg = JSON.parse(fs.readFileSync(path.join(APP, "package.json"), "utf8")); app.setName(pkg.productName || pkg.name); app.getVersion = () => pkg.version; app.on("ready", () => mark("app", "ready")); app.on("web-contents-created", (_e, wc) => { const id = wc.id; mark("wc", "created #" + id); wc.once("did-finish-load", () => { let u = ""; try { u = wc.getURL(); } catch {} mark("wc", `loaded #${id} ${u.replace(/^file:\/\/\/.*\//, "file:").slice(0, 90)}`); }); }); const metrics = (label) => { try { const m = app.getAppMetrics(); const kb = m.reduce((a, p) => a + (p.memory?.privateBytes || 0), 0); mark("metrics", label, { processes: m.length, renderers: m.filter((p) => p.type === "Tab").length, privateMB: Math.round(kb / 1024) }); } catch {} }; setTimeout(() => metrics("5s"), 5000); setTimeout(() => metrics("15s"), 15000); const AddonHostProto = require(path.join(APP, "addons-host.js")).AddonHost.prototype, origAct = AddonHostProto._activateOne; AddonHostProto._activateOne = function (manifest) { const s = performance.now(); try { return origAct.apply(this, arguments); } finally { mark("addon", manifest.id, { ms: Math.round(performance.now() - s) }); } }; mark("trace", "main.js require start"); require(path.join(APP, "main.js")); mark("trace", "main.js require done"); setTimeout(() => { metrics("end"); fs.writeFileSync(OUT, JSON.stringify(ev.sort((a, b) => a.t - b.t), null, 1)); app.exit(0); }, DURATION);