When viewing a single instance of a span in the BugSnag dashboard, a waterfall diagram will show you that instance’s children and their children, and so on, where the children are spans started from within the context of their parent. This page describes the tools available in the SDK to control these parent-child relationships.
By default, when a span is created it becomes the parent span for other spans that start on the same thread. We call this parent span the “current span context” and it remains until it is ended or another span becomes the current context.
For example, in the following snippet span1
becomes the current span context and span2
automatically becomes a child of span1
. span3
then becomes the new current context, but with no parent span because span1
and span2
have already ended.
Span span1 = BugsnagPerformance.startSpan("span1");
Span span2 = BugsnagPerformance.startSpan("span2");
span2.end();
span1.end();
Span span3 = BugsnagPerformance.startSpan("span3");
span3.end();
var span1 = BugsnagPerformance.startSpan("span1")
var span2 = BugsnagPerformance.startSpan("span2")
span2.end()
span1.end()
var span3 = BugsnagPerformance.startSpan("span3")
span3.end()
To prevent a span becoming the new current context when it is created, and so being the parent of future spans:
BugsnagPerformance.startSpan("childless", SpanOptions.createAsCurrentContext(false));
BugsnagPerformance.startSpan("childless", SpanOptions.makeCurrentContext(false))
For example, in the following snippet span1
becomes the parent of both span2
and span3
:
Span span1 = BugsnagPerformance.startSpan("span1");
Span span2 = BugsnagPerformance.startSpan("span2",
SpanOptions.createAsCurrentContext(false));
Span span3 = BugsnagPerformance.startSpan("span3");
span3.end();
span2.end();
span1.end();
val span1 = startSpan("span1")
val span2 = startSpan(
"span2",
SpanOptions.DEFAULTS.makeCurrentContext(false),
)
val span3 = startSpan("span3")
span3.end()
span2.end()
span1.end()
You can set a parent of a span manually using the within
span option:
BugsnagPerformance.startSpan("child-span", SpanOptions.createWithin(manualParentSpan));
BugsnagPerformance.startSpan("child-span", SpanOptions.within(manualParentSpan))
If a span is known to be independent of any other open spans, you can also set the within
span option to null
, which will prevent it becoming a child:
BugsnagPerformance.startSpan("top-level", SpanOptions.createWithin(null));
BugsnagPerformance.startSpan("top-level", SpanOptions.within(null))
If you want operations on another thread to become child spans of the operation that spawns them (e.g. because an operation on the spawning thread is dependent on the spawned operations) then the current context must be passed to spawned threads when they are created so that spans inside the new thread become children of the parent operation.
In the example below, span2
would not be shown as a child of span1
without the additional span option:
Span span1 = BugsnagPerformance.startSpan("span1");
ThreadPoolExecutor executor = new ThreadPoolExecutor(/* */);
executor.submit(
new Runnable() {
@Override
public void run() {
Span span2 = BugsnagPerformance.startSpan("span2",
SpanOptions.createWithin(span1));
}
}
);
var span1 = BugsnagPerformance.startSpan("span1")
val executor = ThreadPoolExecutor(/* */)
executor.submit {
var span2 = BugsnagPerformance.startSpan("span2", SpanOptions.within(span1))
}
Without this, the spans in the thread are not connected to the context of the spawning operation.
However we provide the following tools to pass the context across threads more easily:
We provide convenience functions for the Java standard Runnable
and Callable
interfaces. These can be used to wrap tasks when submitting them to Executor
s or other targets that are not aware of the BugSnag SpanContext
:
runOnUiThread(SpanContext.getCurrent().wrap(new DisplayResults(result)));
runOnUiThread(SpanContext.current.wrap(DisplayResults(result)))
Also in the SDK are context-aware implementations of the standard java.util.concurrent
executor classes that act as a drop-in replacement: setting the appropriate SpanContext
before a task is executed, and cleaning up afterwards:
For java.util.concurrent.ThreadPoolExecutor
:
ContextAwareThreadPoolExecutor executor =
new ContextAwareThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
executor.submit(
new Runnable() {
@Override
public void run() {
// the current SpanContext is automatically propagated to here from the
// spawning thread
}
}
);
val executor = ContextAwareThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS, ArrayBlockingQueue(10))
executor.submit {
// the current SpanContext is automatically propagated to here from the spawning thread
}
For java.util.concurrent.ScheduledThreadPoolExecutor
:
ContextAwareScheduledThreadPoolExecutor scheduledExecutor =
new ContextAwareScheduledThreadPoolExecutor(2);
scheduledExecutor.schedule(
new Runnable() {
@Override
public void run() {
// the current SpanContext is automatically propagated to here from the
// spawning thread
}
}, 5, TimeUnit.SECONDS
);
val scheduledExecutor = ContextAwareScheduledThreadPoolExecutor(2)
scheduledExecutor.schedule(
{
// the current SpanContext is automatically propagated to here from the
// spawning thread
},
5, TimeUnit.SECONDS
)
If you use coroutines you can use our CoroutineContext
implementation that, when added to a coroutine context, will propagate the current SpanContext
across the coroutine suspend
and resume
boundaries:
First add the bugsnag-android-performance-coroutines
module to your project:
dependencies {
implementation "com.bugsnag:bugsnag-android-performance:X.Y.Z"
implementation "com.bugsnag:bugsnag-android-performance-coroutines:X.Y.Z"
}
dependencies {
implementation("com.bugsnag:bugsnag-android-performance:X.Y.Z")
implementation("com.bugsnag:bugsnag-android-performance-coroutines:X.Y.Z")
}
Then you can use it as follows to pass through the current context to your coroutines:
launch(Dispatchers.IO + SpanContext.current.asCoroutineElement()) {
// the current SpanContext is carried over the "launch" boundary from the spawning thread
}
A simple CoroutineScope
implementation is also provided to do this automatically:
class MyActivity: AppCompatActivity(), CoroutineScope by BugsnagPerformanceScope() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
launch {
// the current SpanContext is carried over the "launch" boundary from the
// spawning thread
}
}
}