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_path finder 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

begin(→ None)

Start recording before the public launch imports or registers Qt.

cancel_interactive(→ int)

Retire unfinished readiness probes matching name / detail.

elapsed(→ float)

Seconds on the instrumentation clock without building a snapshot.

event_loop_started(→ None)

Record the first callback actually delivered by the Qt event loop.

interval_started(→ Optional[float])

Return an absolute start time for a user-visible interaction.

last_gui_beat_at(→ Optional[float])

Elapsed timestamp of the latest watchdog beat, or None if absent.

mark(→ None)

Record an instant. Cheap enough to leave in hot paths.

process_started_at(→ Optional[float])

The absolute clock origin used by this timing session, when enabled.

report(→ str)

The timeline, as text.

snapshot(→ dict)

Return the complete timing state as a JSON-serialisable artifact.

span(name[, detail])

Time a named region, nested under whatever encloses it.

stalls_between(→ List[dict])

Return watchdog gaps clipped to their overlap with one interval.

subscribe_readiness(→ None)

Call callback after each post-paint interactive-ready record.

unsubscribe_readiness(→ None)

Remove a callback installed by subscribe_readiness().

watch_interactive(widget, name[, detail, started_at, ...])

Observe when widget is genuinely painted and operable.

watch_the_gui_thread([parent])

Start the stall watchdog. Returns the timer, or None when off.

write_json(→ str)

Write snapshot() to path and return it, or "" on error.

write_report(→ str)

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_START as a wall clock captured immediately before spawning this interpreter. The child translates its age onto time.perf_counter(); unlike sharing a raw monotonic value, that also works on Python 3.9 Windows. An ordinary SPACR_TIMING=1 spacr starts 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.

launch schedules this with a zero-delay timer immediately before QApplication.exec. Unlike a mark placed before exec(), 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.

None while 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 None if 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_TIMING is on.

spacr.qt.timing.process_started_at() → float | None[source]

The absolute clock origin used by this timing session, when enabled.

spacr.qt.timing.report() → str[source]

The timeline, as text.

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_TIMING is 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_ms and add the honest in-window portion as overlap_ms.

Parameters:
  • started_at – start of the interval, in seconds on the instrumentation clock (the clock of the stall rows’ at values).

  • 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 callback after 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 widget is 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 None records 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() to path and return it, or "" on error.

Parameters:

path – destination JSON file, overwritten; nothing is written while timing is off or when the path is empty.

spacr.qt.timing.write_report(path: str = '') → str[source]

Write the timeline. Returns the path, or “” when timing is off.

Nested helpers

_install_import_timer._timed(original)

Wrap a loader’s exec_module so 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 widget and 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() before exec() 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