spacr.qt.timing¶
What the application actually did, and when, with nothing inferred.
Switched on with SPACR_TIMING=1. Off, every function here is a few
attribute lookups and nothing is imported, allocated or written.
WHY THIS EXISTS. Every measurement of spaCR’s start-up so far was taken in a script that imitated the application – built a MainWindow, called a method, timed it – and each one answered a question next to the one being asked. A script cannot see the preloader thread competing with a click, a stylesheet reapplied four times, or a lazy import that fires the first time a user opens a panel. This records the real process.
FOUR THINGS ARE RECORDED, all with real wall-clock times:
SPANS – named, nested regions of work.
with span("build mask"):IMPORTS – every module import over a threshold, with the importing frame, through a
sys.meta_pathfinder rather than by patching__import__.GUI STALLS – a timer that should fire every 16 ms, recording every time it is late. This is the only honest measure of “the app froze”: it is the event loop reporting on itself.
MARKS – instants worth a line, like “window shown”.
The report is a TIMELINE, not a profile. A profile says which function used the CPU; this says what the user was waiting for and for how long, which is a different question and the one being asked.
Functions¶
|
Start recording before the public launch imports or registers Qt. |
|
Retire unfinished readiness probes matching |
|
Seconds on the instrumentation clock without building a snapshot. |
|
Record the first callback actually delivered by the Qt event loop. |
|
Return an absolute start time for a user-visible interaction. |
|
Elapsed timestamp of the latest watchdog beat, or |
|
Record an instant. Cheap enough to leave in hot paths. |
|
The absolute clock origin used by this timing session, when enabled. |
|
The timeline, as text. |
|
Return the complete timing state as a JSON-serialisable artifact. |
|
Time a named region, nested under whatever encloses it. |
|
Return watchdog gaps clipped to their overlap with one interval. |
|
Call |
|
Remove a callback installed by |
|
Observe when |
|
Start the stall watchdog. Returns the timer, or None when off. |
|
Write |
|
Write the timeline. Returns the path, or "" when timing is off. |
Module Contents¶
- spacr.qt.timing.begin() None[source]¶
Start recording before the public launch imports or registers Qt.
A benchmark wrapper may provide
SPACR_TIMING_PROCESS_STARTas a wall clock captured immediately before spawning this interpreter. The child translates its age ontotime.perf_counter(); unlike sharing a raw monotonic value, that also works on Python 3.9 Windows. An ordinarySPACR_TIMING=1 spacrstarts at this module’s own import, still before the expensive Qt application path.
- spacr.qt.timing.cancel_interactive(*, name: str = '', detail: str = '') int[source]¶
Retire unfinished readiness probes matching
name/detail.
- spacr.qt.timing.elapsed() float[source]¶
Seconds on the instrumentation clock without building a snapshot.
- spacr.qt.timing.event_loop_started() None[source]¶
Record the first callback actually delivered by the Qt event loop.
launchschedules this with a zero-delay timer immediately beforeQApplication.exec. Unlike a mark placed beforeexec(), reaching this function proves that the loop has begun dispatching events.
- spacr.qt.timing.interval_started(name: str, detail: str = '') float | None[source]¶
Return an absolute start time for a user-visible interaction.
Nonewhile timing is disabled keeps the ordinary navigation path to one branch and no allocation. The absolute value is deliberately opaque to callers;watch_interactive()turns it into a report duration.- Parameters:
name – the interaction’s label; a
"<name> requested"mark is recorded for it.
- spacr.qt.timing.last_gui_beat_at() float | None[source]¶
Elapsed timestamp of the latest watchdog beat, or
Noneif absent.
- spacr.qt.timing.mark(name: str, detail: str = '') None[source]¶
Record an instant. Cheap enough to leave in hot paths.
- Parameters:
name – label of the instant in the timeline; recorded only while
SPACR_TIMINGis on.
- spacr.qt.timing.process_started_at() float | None[source]¶
The absolute clock origin used by this timing session, when enabled.
- spacr.qt.timing.snapshot() dict[source]¶
Return the complete timing state as a JSON-serialisable artifact.
- spacr.qt.timing.span(name: str, detail: str = '')[source]¶
Time a named region, nested under whatever encloses it.
Records even when the body raises: a span that only appears on success hides exactly the slow failures worth seeing.
- Parameters:
name – label of the region in the timeline; recorded only while
SPACR_TIMINGis on.
- spacr.qt.timing.stalls_between(started_at: float, ended_at: float, stalls: List[dict] | None = None) List[dict][source]¶
Return watchdog gaps clipped to their overlap with one interval.
A watchdog beat can begin before a click and end after it. Charging that whole gap to the click can report a multi-second freeze for an interaction that lasted only a few hundred milliseconds. Preserve the raw interval in
late_msand add the honest in-window portion asoverlap_ms.- Parameters:
started_at – start of the interval, in seconds on the instrumentation clock (the clock of the stall rows’
atvalues).ended_at – end of the interval on the same clock; an end at or before the start gives an empty list.
- spacr.qt.timing.subscribe_readiness(callback: Callable[[dict], None]) None[source]¶
Call
callbackafter each post-paint interactive-ready record.- Parameters:
callback – called with a copy of each readiness record dict; registering the same callable twice has no extra effect, and an exception it raises is ignored.
- spacr.qt.timing.unsubscribe_readiness(callback: Callable[[dict], None]) None[source]¶
Remove a callback installed by
subscribe_readiness().- Parameters:
callback – the callable to remove; one that is not subscribed is ignored.
- spacr.qt.timing.watch_interactive(widget, name: str, detail: str = '', *, started_at: float | None = None, budget_s: float | None = None, on_ready: Callable[[], None] | None = None)[source]¶
Observe when
widgetis genuinely painted and operable.Readiness requires all of the following, observed rather than inferred:
Qt has delivered a callback after the application event loop began;
the screen’s visible widget tree has delivered a paint event;
at least one enabled, visible, non-zero-sized input control has painted;
one further event-loop turn has run after those paint events.
The observer is installed after construction but before the new page can paint. It removes itself at the first valid state and is parented to the observed widget, so neither a report nor a failed screen keeps a window alive. PySide6 is imported only when timing or an explicit readiness callback is requested. A callback without timing creates no report.
- Parameters:
widget – the screen to observe; it and its input controls get the paint filter, and
Nonerecords nothing.name – the readiness record’s label; an unfinished probe with the same name and detail is retired first.
on_ready – optional callback after the real post-paint checkpoint and any enabled timing observers, including when timing is disabled.
- spacr.qt.timing.watch_the_gui_thread(parent=None)[source]¶
Start the stall watchdog. Returns the timer, or None when off.
A QTimer asked for 16 ms records how late it actually was. THIS IS THE ONLY HONEST FREEZE MEASUREMENT: it is the event loop reporting on itself, from inside the real application, so it cannot miss a stall the way an outside script can.
- spacr.qt.timing.write_json(path: str) str[source]¶
Write
snapshot()topathand return it, or""on error.- Parameters:
path – destination JSON file, overwritten; nothing is written while timing is off or when the path is empty.
Nested helpers¶
- _install_import_timer._timed(original)¶
Wrap a loader’s
exec_moduleso each import is measured.spacr/qt/timing.py:379
- _install_import_timer._timed.exec_module(self, module)¶
Import the module, recording how long it took.
spacr/qt/timing.py:381
- watch_interactive._InteractivePaintProbe.__init__(self) None¶
Watch
widgetand its controls until the screen settles.spacr/qt/timing.py:601
- watch_interactive._InteractivePaintProbe._queue_settle(self) None¶
Ask for one settle check on the next event-loop turn.
Coalesced: a burst of paints during layout would otherwise queue a check per paint, and the answer is the same for all of them.
spacr/qt/timing.py:644
- watch_interactive._InteractivePaintProbe._retire(self) None¶
Stop watching. Idempotent, so a second call costs nothing.
spacr/qt/timing.py:743
- watch_interactive._InteractivePaintProbe._settle(self) None¶
Decide whether the screen is interactive yet, and report if so.
spacr/qt/timing.py:675
- watch_interactive._InteractivePaintProbe._usable(control) bool¶
Whether a control is really there to be pressed.
Enabled, visible AND non-empty. A widget with a zero size is laid out but not yet given room, and counting it as usable reports a screen ready before anything can be clicked. A deleted control answers False rather than raising.
spacr/qt/timing.py:656
- watch_interactive._InteractivePaintProbe.eventFilter(self, watched, event)¶
Note a paint on a watched widget, ignoring every other event.
spacr/qt/timing.py:614
- watch_interactive._InteractivePaintProbe.event_loop_started(self) None¶
Discard paints that arrived before the loop began.
A paint delivered by
show()beforeexec()is evidence about the widget but not about THIS contract: readiness begins only once the application event loop has actually dispatched a callback.spacr/qt/timing.py:625
- watch_the_gui_thread._beat()¶
Record that the GUI thread is still answering.
The gap between beats is what a stall is measured as, so this has to be cheap enough that it is never itself the delay.
spacr/qt/timing.py:433