Tracing with Opentelemetry#
Bluesky is instrumented with [OpenTelemetry](https://opentelemetry.io/) tracing span hooks on runs, and on potentially long-running RunEngine methods such as wait()
and collect()
. This can allow you to analyze where your plans are spending a lot of time.
By itself, this doesn’t do anything. To collect tracing output you should configure an exporter in your application or plan, and a collector to send the data to. You can find an example of how to do that [here](https://opentelemetry.io/docs/languages/python/exporters/#usage). Since the @start_as_current_span
decorator from the opentelemetry library doesn’t work for generators, we also provide a @trace_plan
decorator in bluesky.tracing
Tracing messages from the RunEngine
are named "Bluesky RunEngine <method name>"
, e.g. "Bluesky RunEngine wait"
. Traces for runs are tagged with the success
, exit_status
as attributes, as well as the reason
if one is available. Traces for methods are tagged with the content of the message (msg.command
, msg.args
, msg.kwargs
, and msg.obj
). Traces for wait()
also log the group
if one was given, or set no_group_given
true if none was. Ophyd also has traces on Status
objects to easily record how long they live for.
Examples:#
Using the following script, which runs a simple scan and sends traces to the console:
from opentelemetry import trace
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
from ophyd.sim import det1
import bluesky.plan_stubs as bps
import bluesky.preprocessors as bpp
from bluesky.run_engine import RunEngine
from bluesky.tracing import trace_plan, tracer
# Service name is required for most backends,
# and although it's not necessary for console export,
# it's good to set service name anyways.
resource = Resource(attributes={SERVICE_NAME: "bluesky-docs-example"})
traceProvider = TracerProvider(resource=resource)
processor = BatchSpanProcessor(ConsoleSpanExporter())
traceProvider.add_span_processor(processor)
trace.set_tracer_provider(traceProvider)
RE = RunEngine()
@bpp.run_decorator()
@trace_plan(tracer, "demo plan")
def test_tracing_plan():
yield from bps.abs_set(det1._motor, 5)
RE(test_tracing_plan())
We obtain this trace data. Note that the innermost spans are closed first, and therefore printed to the console first. You can see that the "parent_id"
of the Status
span corresponds to the "span_id"
of the set()
span, and so on.
{
"name": "Ophyd Status",
"context": {
"trace_id": "0x6a5abd70e7f74967975ecf64a863a12d",
"span_id": "0x0f94466a988f72ea",
"trace_state": "[]"
},
"kind": "SpanKind.INTERNAL",
"parent_id": "0x4d6e0e523cfdfec8",
"start_time": "2024-06-18T08:40:56.378644Z",
"end_time": "2024-06-18T08:40:56.378896Z",
"status": {
"status_code": "UNSET"
},
"attributes": {
"status_type": "MoveStatus",
"settle_time": 0,
"no_timeout_given": true,
"object_repr": "MoveStatus(done=False, pos=motor1, elapsed=0.0, success=False, settle_time=0.0)",
"device_name": "motor1",
"device_type": "SynAxis",
"kwargs": "{}",
"target": 5,
"start_time": 1718700056.37859,
"start_pos ": 0,
"unit": "mm",
"positioner_name": "motor1",
"positioner": "SynAxis(prefix='', name='motor1', read_attrs=['readback', 'setpoint'], configuration_attrs=['velocity', 'acceleration'])",
"finish_time": 1718700056.3788693,
"finish_pos": 5.0
},
"events": [],
"links": [],
"resource": {
"attributes": {
"service.name": "bluesky-docs-example"
},
"schema_url": ""
}
}
{
"name": "Bluesky RunEngine set",
"context": {
"trace_id": "0x6a5abd70e7f74967975ecf64a863a12d",
"span_id": "0x4d6e0e523cfdfec8",
"trace_state": "[]"
},
"kind": "SpanKind.INTERNAL",
"parent_id": "0x6d4272c02f6990f5",
"start_time": "2024-06-18T08:40:56.378468Z",
"end_time": "2024-06-18T08:40:56.378995Z",
"status": {
"status_code": "UNSET"
},
"attributes": {
"msg.command": "set",
"msg.args": [
5
],
"msg.kwargs": "{\"group\": null}",
"msg.obj": "SynAxis(prefix='', name='motor1', read_attrs=['readback', 'setpoint'], configuration_attrs=['velocity', 'acceleration'])"
},
"events": [],
"links": [],
"resource": {
"attributes": {
"service.name": "bluesky-docs-example"
},
"schema_url": ""
}
}
{
"name": "demo plan",
"context": {
"trace_id": "0x6a5abd70e7f74967975ecf64a863a12d",
"span_id": "0x6d4272c02f6990f5",
"trace_state": "[]"
},
"kind": "SpanKind.INTERNAL",
"parent_id": null,
"start_time": "2024-06-18T08:40:56.378255Z",
"end_time": "2024-06-18T08:40:56.379044Z",
"status": {
"status_code": "UNSET"
},
"attributes": {},
"events": [],
"links": [],
"resource": {
"attributes": {
"service.name": "bluesky-docs-example"
},
"schema_url": ""
}
}
{
"name": "Bluesky RunEngine run",
"context": {
"trace_id": "0xedc3dae09fb45d4982e0af29f053141c",
"span_id": "0xb54c64dcdebaa81a",
"trace_state": "[]"
},
"kind": "SpanKind.INTERNAL",
"parent_id": null,
"start_time": "2024-06-18T08:40:56.377673Z",
"end_time": "2024-06-18T08:40:56.379335Z",
"status": {
"status_code": "UNSET"
},
"attributes": {
"msg.command": "open_run",
"msg.args": [],
"msg.kwargs": "{}",
"msg.no_obj_given": true,
"exit_status": "success",
"reason": ""
},
"events": [],
"links": [],
"resource": {
"attributes": {
"service.name": "bluesky-docs-example"
},
"schema_url": ""
}
}