ommx.tracing#

Per-cell / per-block tracing for OMMX.

Two user-facing APIs, sharing the same OTel collector + renderers:

Jupyter cell magic (best for notebooks):

%load_ext ommx.tracing

%%ommx_trace
instance = Instance.from_bytes(blob)
solution = instance.evaluate(state)

Cell output shows a nested text tree of every span produced during the cell (Rust and Python alike), annotated with durations and attributes, plus a download link for the full trace in Chrome Trace Event Format.

Context manager / decorator (best for scripts, tests, CI):

from ommx.tracing import capture_trace, traced

with capture_trace() as trace:
    solution = instance.evaluate(state)
print(trace.text_tree())
trace.save_chrome_trace("trace.json")

@traced(output="process.json")
def process():
    ...

The public surface is intentionally small:

  • capture_trace — context manager; __enter__ returns a TraceResult placeholder that __exit__ fills in (for success and for exceptions — information is never dropped).

  • TraceResultspans, text_tree(), chrome_trace_json(), save_chrome_trace(path).

  • traced() — decorator sugar on top of capture_trace, optionally writing the Chrome Trace JSON to disk.

  • load_ipython_extension() — wired by %load_ext ommx.tracing; registers the %%ommx_trace cell magic. The collector itself is attached to the active OpenTelemetry TracerProvider lazily, on the first traced cell, so %load_ext stays cheap and cannot fail due to provider state that is still being configured further up the notebook.

  • unload_ipython_extension() — pair for %unload_ext. Kept as a no-op because IPython magics cannot be cleanly unregistered without disturbing user state; leaving the collector installed costs nothing.

Everything else (_collector, _render, _setup, _magic, _capture) is internal and may change without notice. Reach for them only if you are building on top of this module and can tolerate breakage.

Classes#

TraceResult

Populated result of a capture_trace block.

capture_trace

Context manager that captures every OTel span inside the block.

Functions#

load_ipython_extension(→ None)

Register the %%ommx_trace cell magic on ipython.

traced(…)

Decorator that runs the wrapped function under capture_trace.

unload_ipython_extension(→ None)

IPython extension hook, kept as a no-op.

Package Contents#

class TraceResult#

Populated result of a capture_trace block.

Filled in by capture_trace on __exit__ (including the exception path, so the caller can always inspect the trace even when the block raised).

chrome_trace_json() str#

Return a Chrome Trace Event Format JSON string.

save_chrome_trace(path: str | Path) None#

Write the Chrome Trace JSON to path (creating parents as needed).

Overwrites any existing file. The UTF-8 encoding matches the JSON spec and is what Perfetto / speedscope / chrome://tracing all accept.

text_tree() str#

Return the nested text tree — same renderer the cell magic uses.

spans: List[ReadableSpan] = []#
class capture_trace(name: str = _DEFAULT_ROOT_SPAN_NAME)#

Context manager that captures every OTel span inside the block.

The root span is started with an explicit empty OTel Context so each block gets its own fresh trace_id regardless of any ambient spans — the collector keys captures by trace_id, so without this guard sibling spans from unrelated instrumentation would bleed into the result.

load_ipython_extension(ipython: IPython.core.interactiveshell.InteractiveShell) None#

Register the %%ommx_trace cell magic on ipython.

Invoked by IPython when the user runs %load_ext ommx.tracing. Safe to call more than once — later calls are no-ops since IPython dedupes magics by name.

traced(func: _F) _F#
traced(*, name: str | None = ..., output: str | Path | None = ...) Callable[[_F], _F]

Decorator that runs the wrapped function under capture_trace.

Supports all three call shapes:

@traced
def process(): ...

@traced()
def process(): ...

@traced(name="build_qubo", output="qubo.json")
def process(): ...

If output is given, the Chrome Trace JSON is written to that path when the function returns or raises — information is never dropped. The exception, if any, is re-raised unchanged after the file is written.

If name is omitted, the span is named after the function (fn.__qualname__) so traces from multiple decorated functions are easy to tell apart in the rendered tree.

unload_ipython_extension(ipython: IPython.core.interactiveshell.InteractiveShell) None#

IPython extension hook, kept as a no-op.

Removing a previously-registered magic leaves the shell in an awkward state (the name still resolves for tab completion), and there is no user-observable state to tear down — the collector sheds its entries on retrieval already.