// Lightweight UI-side restart/cold-load timing. // // Records a few key marks using performance.now(), then POSTs them to // /api/diag/timing/ui once 'ui.first_state' has fired. Disabled by // setting window.BBCTRL_TRACE = false before this module is loaded. // // Marks collected: // script.load -- this module evaluated // ws.open -- websocket onopen // ws.first_msg -- first message from controller // ui.first_state -- first message that contained controller state // window.load -- window 'load' event // // Aligning these with /api/diag/timing on the server gives the full // picture from systemd start -> bbctrl up -> WS open -> UI rendered. "use strict"; const _enabled = typeof window !== "undefined" && window.BBCTRL_TRACE !== false; const _t0 = (typeof performance !== "undefined" && performance.now) ? performance.now() : Date.now(); const _navStart = (typeof performance !== "undefined" && performance.timeOrigin) ? performance.timeOrigin : Date.now(); const marks = []; let posted = false; function _now() { return (typeof performance !== "undefined" && performance.now) ? performance.now() - _t0 : Date.now() - _t0; } function mark(name, fields) { if (!_enabled) return; marks.push(Object.assign({ n: name, t: Math.round(_now()) }, fields || {})); } function _post() { if (!_enabled || posted) return; posted = true; const body = JSON.stringify({ navStart: _navStart, t0_perf: _t0, href: typeof location !== "undefined" ? location.href : "", ua: typeof navigator !== "undefined" ? navigator.userAgent : "", marks: marks, }); try { if (typeof fetch === "function") { fetch("/api/diag/timing/ui", { method: "PUT", headers: { "Content-Type": "application/json" }, body: body, keepalive: true, }).catch(() => {}); } } catch (e) { /* swallow */ } } // Record window load too; doesn't block posting. if (_enabled && typeof window !== "undefined") { window.addEventListener("load", () => mark("window.load")); } mark("script.load"); module.exports = { enabled: _enabled, mark: mark, onWsOpen: () => mark("ws.open"), onWsFirstMessage: () => mark("ws.first_msg"), onFirstState: () => { mark("ui.first_state"); // Defer slightly so any synchronous render finishes first. setTimeout(_post, 100); }, flush: _post, };