diag: add startup-timing trace and /api/diag/timing endpoint
bbctrl.Trace records monotonic-anchored events from process start. Ctrl, Comm, the Web layer and __init__ are instrumented so a single GET /api/diag/timing returns a full timeline of import, controller init, AVR connection, first websocket, and first GET /. The restart-timing.js client posts performance.now() marks back so the browser side can be aligned in the same view. Used to drive the cold-boot optimisations that reduce listen latency on the Pi by ~8s.
This commit is contained in:
@@ -798,6 +798,32 @@ class RemoteDiagnosticsHandler(bbctrl.APIHandler):
|
||||
'message': e.reason or "Unknown"
|
||||
})
|
||||
|
||||
|
||||
class TimingHandler(bbctrl.APIHandler):
|
||||
"""Return the bbctrl process startup timeline as JSON.
|
||||
|
||||
Includes monotonic-anchored events from bbctrl.Trace, the wall
|
||||
clock anchor (so the timeline can be aligned with journalctl /
|
||||
systemd-analyze output), and the most recent UI-side timing
|
||||
payload posted by the browser.
|
||||
"""
|
||||
def get(self):
|
||||
import bbctrl.Trace as _T
|
||||
self.write_json(_T.timeline())
|
||||
|
||||
|
||||
class UITimingHandler(bbctrl.APIHandler):
|
||||
"""Browser posts its performance.now() marks here once per load."""
|
||||
def put_ok(self):
|
||||
import bbctrl.Trace as _T
|
||||
# self.json is parsed in APIHandler.prepare()
|
||||
try:
|
||||
_T.set_ui_timing(self.json)
|
||||
_T.mark('ui.posted_timing',
|
||||
marks=len(self.json.get('marks', []) or []))
|
||||
except Exception: pass
|
||||
|
||||
|
||||
# Base class for Web Socket connections
|
||||
class ClientConnection(object):
|
||||
def __init__(self, app):
|
||||
@@ -873,6 +899,12 @@ class SockJSConnection(ClientConnection, sockjs.tornado.SockJSConnection):
|
||||
ip = info.ip
|
||||
if 'X-Real-IP' in info.headers: ip = info.headers['X-Real-IP']
|
||||
self.app.get_ctrl(id).log.get('Web').info('Connection from %s' % ip)
|
||||
try:
|
||||
if not getattr(self.app, '_first_ws', False):
|
||||
self.app._first_ws = True
|
||||
import bbctrl.Trace as _T
|
||||
_T.mark('ws.first_open', ip=ip)
|
||||
except Exception: pass
|
||||
super().on_open(id)
|
||||
|
||||
|
||||
@@ -881,6 +913,23 @@ class StaticFileHandler(tornado.web.StaticFileHandler):
|
||||
self.set_header('Cache-Control',
|
||||
'no-store, no-cache, must-revalidate, max-age=0')
|
||||
|
||||
def prepare(self):
|
||||
# Mark the first request for the index page so we can see when
|
||||
# chromium actually started fetching the UI on cold boot.
|
||||
try:
|
||||
app = self.application
|
||||
if not getattr(app, '_first_root_get', False):
|
||||
# Treat any GET '/' or '/index.html' as the root fetch.
|
||||
p = self.request.path
|
||||
if p in ('/', '/index.html', ''):
|
||||
app._first_root_get = True
|
||||
import bbctrl.Trace as _T
|
||||
_T.mark('web.first_root_get',
|
||||
ip=self.request.remote_ip,
|
||||
ua=(self.request.headers.get('User-Agent') or '')[:60])
|
||||
except Exception: pass
|
||||
return super().prepare()
|
||||
|
||||
class Web(tornado.web.Application):
|
||||
def __init__(self, args, ioloop):
|
||||
self.args = args
|
||||
@@ -902,6 +951,8 @@ class Web(tornado.web.Application):
|
||||
|
||||
handlers = [
|
||||
(r'/websocket', WSConnection),
|
||||
(r'/api/diag/timing', TimingHandler),
|
||||
(r'/api/diag/timing/ui', UITimingHandler),
|
||||
(r'/api/log', LogHandler),
|
||||
(r'/api/message/(\d+)/ack', MessageAckHandler),
|
||||
(r'/api/bugreport', BugReportHandler),
|
||||
|
||||
Reference in New Issue
Block a user