Android performance integration guide

Step-by-step instructions for adding performance monitoring to your Android projects.

The BugSnag Performance Android integration automatically instruments app starts, activity and fragment loads and okhttp network requests. Arbitrary operations can also be manually instrumented in your code.

New to BugSnag? Create an account

Looking for error monitoring? See our integration guide

Installation

Add the following dependencies to your Module Gradle Settings, usually found at <project_dir>/app/build.gradle or build.gradle.kts:

dependencies {
    // ...
    implementation "com.bugsnag:bugsnag-android-performance:1.+"
}
dependencies {
    // ...
    implementation("com.bugsnag:bugsnag-android-performance:1.+")
}

The latest available version of bugsnag-android-performance is v1.10.0.

Basic configuration

Configure your API key in the <application> tag of your App Manifest file (usually in src/main/AndroidManifest.xml):

<application ...>
  <meta-data android:name="com.bugsnag.android.API_KEY"
             android:value="your-api-key-here"/>
</application>

This is the same API key and the same metadata key used by the BugSnag Error Monitoring library.

You can find your API key in Project Settings from your BugSnag dashboard.

Application

Initialize BugSnag Performance in the onCreate callback of your Application subclass:

public class MyApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        BugsnagPerformance.start(this);
    }
}
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        BugsnagPerformance.start(this)
    }
}

Instrumenting app starts

BugSnag will automatically detect and report app starts, measuring them until the first participating “Screen load” span has ended (see below for details). These timings are shown under the “App starts” tab in the BugSnag Performance dashboard.

We recommend you enhance your app start measurement by manually reporting when the Application class is initialized:

public class MyApp extends Application {
    static {
        BugsnagPerformance.reportApplicationClassLoaded();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        BugsnagPerformance.start(PerformanceConfiguration.load(this));
    }
}
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        BugsnagPerformance.start(PerformanceConfiguration.load(this))
    }

    companion object {
        init {
            BugsnagPerformance.reportApplicationClassLoaded()
        }
    }
}

The instrumentation determines whether the app is being launched for the first time since installation or reboot and so is a full “cold” start, or whether this is a “warm” start where the Application is already loaded, or a “hot” start where the previous launch is simply being resumed. These three app start types are displayed separately on your BugSnag dashboard.

If you have Activities or Fragments that you wish to consider part of your app start period, use the doNotEndAppStart configuration option or @DoNotEndAppStart annotation. You can also disable the automatic instrumentation of app starts, using autoInstrumentAppStarts configuration option.

Instrumenting Activity and Fragment loads

Automatic instrumentation

BugSnag will automatically detect and report Activity loads, measuring the time taken between the start of onCreate and the end of onResume. These timings are shown under the “Screen loads” tab in the BugSnag Performance dashboard.

If you use Fragments in your app, you can also add the bugsnag-android-performance-appcompat module to your project to automatically detect parts of the Fragment lifecycle:

dependencies {
    implementation "com.bugsnag:bugsnag-android-performance:1.+"
    implementation "com.bugsnag:bugsnag-android-performance-appcompat:1.+"
}
dependencies {
    implementation("com.bugsnag:bugsnag-android-performance:1.+")
    implementation("com.bugsnag:bugsnag-android-performance-appcompat:1.+")
}

If you are using ProGuard, DexGuard or R8, in order to see the full name of the Fragments that we instrument, you’ll need to tell ProGuard explicitly to not strip this information by adding the following rule to your ProGuard configuration:

-keep class * extends androidx.fragment.app.Fragment

To prevent certain Activities and Fragments from being instrumented, use the doNotAutoInstrument configuration option or @DoNotAutoInstrument annotation. Alternatively, to disable automatic instrumentation of activities entirely, or to control the point at when an activity should be defined as loaded, see the autoInstrumentActivities configuration option.

Jetpack Compose instrumentation

Due to the nature of Jetpack Compose layouts, it is not possible to automatically instrument these views. However the bugsnag-android-performance-compose module providers a wrapper Composable to assist you in instrumenting your views.

First, add the module as a dependency to your project:

dependencies {
    implementation "com.bugsnag:bugsnag-android-performance:1.+"
    implementation "com.bugsnag:bugsnag-android-performance-compose:1.+"
}
dependencies {
    implementation("com.bugsnag:bugsnag-android-performance:1.+")
    implementation("com.bugsnag:bugsnag-android-performance-compose:1.+")
}

Then instrument your Composables by wrapping them in a MeasuredComposable and providing a name to represent it on the dashboard:

MeasuredComposable(name = "LoginScreen") {
    LoginScreen()
}

MeasuredComposable wraps a standard Box layout for its instrumentation.

Manual instrumentation

To instrument view loads manually, use startViewLoadSpan and endViewLoadSpan passing the Activity:

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    BugsnagPerformance.startViewLoadSpan(this);
}

public void onResume() {
    super.onResume();
    BugsnagPerformance.endViewLoadSpan(this);
}
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    BugsnagPerformance.startViewLoadSpan(this)
}

override fun onResume() {
    super.onResume()
    BugsnagPerformance.endViewLoadSpan(this)
}

Alternatively, the span can be created by passing individual attributes for the type and name. You should ensure the span is ended by holding a reference to the returned span and calling end in an appropriate event handler:

// at the start of a view loading:
Span viewSpan = BugsnagPerformance.startViewLoadSpan(
    ViewType.ACTIVITY,
    "MyActivity"
);
// in an "onResume" or similar:
viewSpan.end();
// at the start of a view loading:
val viewSpan = BugsnagPerformance.startViewLoadSpan(
    ViewType.ACTIVITY,
    "MyActivity"
)
// in an "onResume" or similar:
viewSpan.end()

Instrumenting network requests

Automatic instrumentation

BugSnag can automatically detect and report network requests from OkHttp using the bugsnag-android-performance-okhttp module. These timings are shown under the “Network requests” tab in the BugSnag Performance dashboard.

To start capturing network requests, add bugsnag-android-performance-okhttp as a dependency to your project:

dependencies {
    implementation "com.bugsnag:bugsnag-android-performance:1.+"
    implementation "com.bugsnag:bugsnag-android-performance-okhttp:1.+"
    implementation "com.squareup.okhttp3:okhttp:4.+"
}
dependencies {
    implementation("com.bugsnag:bugsnag-android-performance:1.+")
    implementation("com.bugsnag:bugsnag-android-performance-okhttp:1.+")
    implementation("com.squareup.okhttp3:okhttp:4.+")
}

BugSnag supports OkHttp versions 3, 4 and 5.

Then configure the BugSnag OkHttp EventListener on your OkHttpClient:

OkHttpClient client = new OkHttpClient.Builder()
    .eventListener(new BugsnagPerformanceOkhttp())
    .build();
val client = OkHttpClient.Builder()
    .eventListener(BugsnagPerformanceOkhttp())
    .build()

The networkRequestCallback configuration option allows you to control the data sent in these network request spans using a callback.

Manual instrumentation

To instrument network requests manually use startNetworkRequestSpan:

URL url = new URL("https://example.com/api");
try (Span span = BugsnagPerformance.startNetworkRequestSpan(url, "GET")) {
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    NetworkRequestAttributes.setResponseCode(span, connection.getResponseCode());
    NetworkRequestAttributes.setResponseContentLength(span, connection.getContentLengthLong());

    // consume the connection as normal
}
val url = URL("https://example.com/api")
BugsnagPerformance.startNetworkRequestSpan(url, "GET").use { span ->
    val connection = url.openConnection() as HttpURLConnection
    NetworkRequestAttributes.setResponseCode(span, connection.responseCode)
    NetworkRequestAttributes.setResponseContentLength(span, connection.contentLengthLong)

    // consume the connection as normal
}

Sending custom spans

To send custom spans to BugSnag for any other operations you wish to measure, use BugsnagPerformance.startSpan to start a span, and call the span’s end method to end the measurement:

try (BugsnagPerformance.startSpan("login")) {
    LoginApi.login();
}
BugsnagPerformance.startSpan("login").use {
    LoginApi.login()
}

Span is Closable and can be used either in a try-with-resources (in Java), or with the use function (in Kotlin)

A Span can also be manually ended using its end function if the measured process cannot be encapsulated in a single block:

Span span = BugsnagPerformance.startSpan("login");
LoginApi.login(() -> {
    // login is complete
    span.end();
});
val span = BugsnagPerformance.startSpan("login")
LoginApi.login {
    // login is complete
    span.end()
}

The options parameter allows you to customize some elements of the span’s behavior:

Reporting child-only spans

If a custom span is “first class”, its performance characteristics will be aggregated and shown in the “Custom” tab of the BugSnag dashboard. If the custom span is useful only for adding insight into the performance of its parent (through the waterfall diagram on the span instance page), you should set the isFirstClass span option to false:

BugsnagPerformance.startSpan("always-nested", SpanOptions.DEFAULTS.setFirstClass(false));
BugsnagPerformance.startSpan("always-nested", SpanOptions.DEFAULTS.setFirstClass(false))

Controlling span hierarchies

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. This allows you to see in more detail where the time was spent, and aid diagnosing and fixing any problems. When creating your own spans, you can use the options parameter to control their parent-child relationships to produce a meaningfully representative hierarchy in your dashboard. See Maintaining span context for more information.

Start and end time overrides

By default, a span will use the current timestamp as its start time. However, you can use the startTime span option to report spans that have already started by providing your own timestamp. You can also end a span with your own timestamp to provide a retrospective end time.

The values applied need to be relative to SystemClock.elapsedRealtimeNanos().

long activityStartTime = SystemClock.elapsedRealtimeNanos();

// When the activity has completed:
long activityEndTime = SystemClock.elapsedRealtimeNanos();

BugsnagPerformance.startSpan("retrospective-span", SpanOptions.DEFAULTS.startTime(activityStartTime))
    .end(activityEndTime);
var activityStartTime = SystemClock.elapsedRealtimeNanos()

// When the activity has completed:
var activityEndTime = SystemClock.elapsedRealtimeNanos()

BugsnagPerformance.startSpan("retrospective-span", SpanOptions.DEFAULTS.startTime(activityStartTime))
    .end(activityEndTime)

Adding custom span attributes

Additional data can be added to spans to help diagnose performance issues. These are sent as span attributes and will be displayed on your Performance dashboard alongside the automatic span data captured by the SDK.

Screenshot of custom attributes panel

Attributes are added via a span object and are name value pairs where the type can be either a string, integer, double, boolean or an array of one of these types:

Span span = BugsnagPerformance.startSpan("span-with-data");
span.setAttribute("api.protocol", "gql");
span.setAttribute("api.version", "v2");
var span = BugsnagPerformance.startSpan("span-with-data")
span.setAttribute("api.protocol", "gql");
span.setAttribute("api.version", "v2");

You can use a callback to set attributes when they are ending. This allows you to access all spans being generated, including those from automatic instrumentation:

PerformanceConfiguration config = PerformanceConfiguration.load(this);
config.addOnSpanEndCallback(new OnSpanEndCallback() {
    @Override
    public boolean onSpanEnd(Span span) {
        span.setAttribute("device.locale", "en-US");
        return true;
    }
});
BugsnagPerformance.start(config);
BugsnagPerformance.start(PerformanceConfiguration.load(this).apply {
    addOnSpanEndCallback { span ->
        span.setAttribute("device.locale", "en-US")
        true
    }
})

The number and size of attributes added to spans are limited by the SDK to avoid oversized span payloads that would be rejected by the BugSnag API. See the Custom attribute limit configuration options to see how this can be customized if more data is required.

These callbacks can be executed with a high frequency, so care should be taken not to perform complex operations that will have a detrimental effect on performance. For more information, see the span callbacks configuration option.