Restart timing: bbctrl.Trace, /api/diag/timing, UI marks
Add a lightweight, self-contained phase tracer for measuring end-to-end bbctrl restart and Pi boot time. Disabled by setting BBCTRL_TRACE=0. - src/py/bbctrl/Trace.py: monotonic-anchored event log + sd_notify helper. - bbctrl/__init__.py: marks for imports, args parsed, ioloop, web init, listen, and an sd_notify READY=1 once HTTP is bound. - bbctrl/Ctrl.py: spans around each subsystem (avr, i2c, lcd, mach, preplanner, jog, pwr, hooks, aux, mach.connect). - bbctrl/Comm.py: avr.firmware_rebooted mark. - bbctrl/Web.py: TimingHandler (GET /api/diag/timing) and UITimingHandler (PUT /api/diag/timing/ui), plus a ws.first_open mark. - src/js/restart-timing.js + app.js: UI-side performance.now() marks (script.load, ws.open, ws.first_msg, ui.first_state, window.load), posted once to the controller. - scripts/bbctrl.service: stdout/stderr -> journal so TRACE lines are visible via journalctl -u bbctrl. (Was StandardOutput=null.) Revert: git revert this commit. To disable at runtime without reverting, set BBCTRL_TRACE=0 in the bbctrl service environment.
This commit is contained in:
@@ -4,6 +4,7 @@ const api = require("./api");
|
||||
const cookie = require("./cookie")("bbctrl-");
|
||||
const Sock = require("./sock");
|
||||
const semverLt = require("semver/functions/lt");
|
||||
const restartTiming = require("./restart-timing");
|
||||
|
||||
if (document.getElementById("svelte-dialog-host") != undefined) {
|
||||
SvelteComponents.createComponent(
|
||||
@@ -497,11 +498,19 @@ module.exports = new Vue({
|
||||
connect: function() {
|
||||
this.sock = new Sock(`//${location.host}/sockjs`);
|
||||
|
||||
let _gotFirstMsg = false;
|
||||
let _gotFirstState = false;
|
||||
|
||||
this.sock.onmessage = (e) => {
|
||||
if (typeof e.data != "object") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_gotFirstMsg) {
|
||||
_gotFirstMsg = true;
|
||||
restartTiming.onWsFirstMessage();
|
||||
}
|
||||
|
||||
if (e.data.log && e.data.log.msg !== "Switch not found") {
|
||||
this.$broadcast("log", e.data.log);
|
||||
|
||||
@@ -511,6 +520,11 @@ module.exports = new Vue({
|
||||
}
|
||||
}
|
||||
|
||||
if (!_gotFirstState) {
|
||||
_gotFirstState = true;
|
||||
restartTiming.onFirstState();
|
||||
}
|
||||
|
||||
// Check for session ID change on controller
|
||||
if ("sid" in e.data) {
|
||||
if (typeof this.sid == "undefined") {
|
||||
@@ -535,6 +549,7 @@ module.exports = new Vue({
|
||||
|
||||
this.sock.onopen = () => {
|
||||
this.status = "connected";
|
||||
restartTiming.onWsOpen();
|
||||
this.$emit(this.status);
|
||||
this.$broadcast(this.status);
|
||||
};
|
||||
|
||||
80
src/js/restart-timing.js
Normal file
80
src/js/restart-timing.js
Normal file
@@ -0,0 +1,80 @@
|
||||
// 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,
|
||||
};
|
||||
Reference in New Issue
Block a user