ommx.tracing#
Inspect the time spent inside an OMMX operation.
A span records the start and end of an instrumented operation. Nested spans show where time went within a parent operation. The parent includes its children's time. A single root span tells you only the total duration.
Capture one evaluation
>>> from ommx import Instance
>>> from ommx.tracing import capture_trace, render_text_tree, save_chrome_trace
>>> problem = Instance.minimize()
>>> x = problem.new_binary("x")
>>> problem.objective = x
>>> with capture_trace("evaluate candidate") as trace:
... solution = problem.evaluate({x.id: 1})
>>> assert solution.objective == 1
>>> assert "evaluate candidate" in render_text_tree(trace)
Inspect the tree in a terminal or display the result in Jupyter. To view a timeline in Perfetto or another Chrome trace viewer, save the trace as JSON:
>>> from pathlib import Path
>>> from tempfile import TemporaryDirectory
>>> with TemporaryDirectory() as directory:
... path = Path(directory) / "trace.json"
... save_chrome_trace(trace, path)
... assert path.stat().st_size > 0
To trace each call to a function, use traced(). The function still
returns its normal result. On failure, the decorator attempts to save the
trace and propagates the original exception.
>>> from ommx.tracing import traced
>>> with TemporaryDirectory() as directory:
... path = Path(directory) / "evaluate.json"
... @traced(output=path)
... def evaluate(value):
... return problem.evaluate({x.id: value})
... assert evaluate(0).objective == 0 and path.is_file()
Trace one notebook cell
In a Jupyter kernel with the SDK installed, run this line in its own cell:
%load_ext ommx.tracing
Then start the cell you want to measure with the magic:
%%ommx_trace
from ommx import Instance
solution = Instance.minimize().evaluate({})
The output displays the span tree and a Chrome trace JSON link. Only instrumented operations produce child spans. If the cell raises an exception, the trace is displayed and the exception propagates.
For a solver breakdown, see ommx_highs_adapter.OMMXHighsAdapter.
To keep a trace with an Experiment, see ommx.experiment.SealedRun.trace.
Export spans to an OpenTelemetry backend
Local capture works without an external exporter. To send the same spans to
an OpenTelemetry collector, install opentelemetry-exporter-otlp-proto-grpc
in the SDK environment. Set OTEL_EXPORTER_OTLP_ENDPOINT to the collector's
gRPC endpoint and supply credentials if it requires them.
Configure the SDK provider once at the start of the process or notebook kernel, before importing OMMX. If the application already owns a provider, add the span processor to that provider instead. The following standalone script requires a running collector:
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
trace.set_tracer_provider(provider)
from ommx import Instance
from ommx.tracing import capture_trace
with capture_trace("evaluate candidate") as captured:
Instance.minimize().evaluate({})
print(captured)
provider.shutdown()
The local span collector attaches to the active provider, so the block appears in both the local tree and the external backend. In this standalone script, shutdown flushes the provider at process exit. In a service, leave shutdown to the owner of the provider.
Classes#
Populated result of a |
|
Context manager that captures every OTel span inside the block. |
Functions#
|
|
|
Register the |
|
Render |
|
Write |
|
Decorator that runs the wrapped function under |
|
IPython extension hook, kept as a no-op. |
Package Contents#
- class TraceResult#
Populated result of a
capture_traceblock.Filled in by
capture_traceon__exit__(including the exception path, so the caller can always inspect the trace even when the block raised).- classmethod from_otlp_protobuf(payload: bytes) TraceResult#
Build a trace result from an OMMX trace payload.
- request: opentelemetry.proto.collector.trace.v1.trace_service_pb2.ExportTraceServiceRequest#
- class capture_trace(name: str = _DEFAULT_ROOT_SPAN_NAME, tracer_name: str = _TRACER_NAME)#
Context manager that captures every OTel span inside the block.
The root span is started with an explicit empty OTel
Contextso each block gets its own freshtrace_idregardless of any ambient spans — the collector keys captures bytrace_id, so without this guard sibling spans from unrelated instrumentation would bleed into the result.
- chrome_trace_json(result: TraceResult) str#
- load_ipython_extension(ipython: InteractiveShell) None#
Register the
%%ommx_tracecell magic onipython.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.
- render_text_tree(result: TraceResult) str#
Render
resultas a nested ASCII tree, one root per top-level span.The tree preserves parent→child relationships as recorded by OTel. Siblings are sorted by start time so the output reflects execution order.
- save_chrome_trace(result: TraceResult, path: str | Path) None#
Write
resultas Chrome Trace JSON topath.Overwrites any existing file. The UTF-8 encoding matches the JSON spec and is what Perfetto / speedscope /
chrome://tracingall accept.
- traced(func: Callable[..., Any]) Callable[..., Any]#
- traced(*, name: str | None = ..., output: str | Path | None = ...) Callable[[Callable[..., Any]], Callable[..., Any]]
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
outputis given and a trace result is available, saving Chrome Trace JSON to that path is attempted after capture ends:When the wrapped function returns normally, saving completes before its return value is passed to the caller. A save error propagates instead of returning that value.
When the wrapped function raises, saving is best-effort. Save errors derived from
Exceptionare suppressed so that the original exception is re-raised unchanged. A trace file is not guaranteed to exist or be complete. If capture fails before a result is available, no save is attempted.
These rules apply to both synchronous and
async deffunctions. For an async function, capture surrounds the awaited execution. Withoutput=None, no trace file is saved automatically.If
nameis 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: 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.