ommx.tracing
============

.. py:module:: ommx.tracing

.. autoapi-nested-parse::

   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:

   * :class:`capture_trace` — context manager; ``__enter__`` returns a
     :class:`TraceResult` placeholder that ``__exit__`` fills in (for
     success *and* for exceptions — information is never dropped).
   * :class:`TraceResult` — ``spans``, ``text_tree()``,
     ``chrome_trace_json()``, ``save_chrome_trace(path)``.
   * :func:`traced` — decorator sugar on top of :class:`capture_trace`,
     optionally writing the Chrome Trace JSON to disk.
   * :func:`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.
   * :func:`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
-------

.. autoapisummary::

   ommx.tracing.TraceResult
   ommx.tracing.capture_trace


Functions
---------

.. autoapisummary::

   ommx.tracing.load_ipython_extension
   ommx.tracing.traced
   ommx.tracing.unload_ipython_extension


Package Contents
----------------

.. py:class:: TraceResult

   Populated result of a ``capture_trace`` block.

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


   .. py:method:: chrome_trace_json() -> str

      Return a Chrome Trace Event Format JSON string.



   .. py:method:: save_chrome_trace(path: Union[str, pathlib.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.



   .. py:method:: text_tree() -> str

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



   .. py:attribute:: spans
      :type:  List[opentelemetry.sdk.trace.ReadableSpan]
      :value: []



.. py: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.


.. py:function:: 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.


.. py:function:: traced(func: _F) -> _F
                 traced(*, name: Optional[str] = ..., output: Optional[Union[str, pathlib.Path]] = ...) -> Callable[[_F], _F]

   Decorator that runs the wrapped function under :class:`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.


.. py:function:: 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.


