Skip to main content

Performance Tracing

Overview

Embrace’s Performance Tracing solution gives you visibility into any app operation you’d like to track, including duration, success rate, and any contextual metadata collected at runtime that helps debug the root cause of your mobile app's performance issues. With our tool, you can quickly spot any bottlenecks in your app’s architecture, pinpoint areas you need to troubleshoot with high precision, and ultimately deliver a truly optimized user experience.

Feature Support

Minimum Requirements

We recommend using the latest Android SDK version for the most up-to-date API. Even though Performance Tracing is enabled in earlier versions as well, they only support a subset of features described in this doc, which applies to versions 6.4.0 and above.

The Embrace Performance Tracing API allows you to:

  • Create real-time performance timers or record data for past operations.
    • For real-time tracing, we use a “stopwatch” concept that lets you start and stop a span's recording manually.
    • To record past operations, you can specify the start and end times of your spans that you might have captured already.
    • You can mix and match real time and past events by specifying the start and end times when you start and stop your spans.
  • Add child spans to a parent span to track sub-operations within an operation.
  • Attach attributes and span events to each span to give them further context
    • Attributes allow you to specify string key-value pairs that can be useful for filtering, grouping, and deriving custom metrics
    • Span events represent a point in time of the execution of the span and they can also have attributes

There are no limits on the duration of a span as long as the app is running.

There are also no limits to the number of child spans you can have per trace, provided the total number of spans do not exceed the per-session maximum.

Limits

TypeLimit
Max number of spans per session500
Max number of attributes per span50
Max number of events per span10
Max number of attributes per event10
Length of attribute keys50 characters
Length of attribute values200 characters
Length of Span names50 characters
Length of Event names100 characters
Exceeding Limits

If you exceed the listed limits, the operation with the limit-exceeding call will fail. See the API documentation for details.

Naming Conventions

  • Span Names are case-sensitive and are a max of 50 characters.
  • Key Names are case-sensitive, have a max of 50 characters, and are alphanumeric ASCII characters
Internal Prefixes

The emb- and emb. prefixes are reserved for internal Embrace span and attribute names, respectively. You should never create a span or attribute key name with emb- and emb. prefixes

Adding Performance Traces To Your App

To use this feature:

  1. Ensure you’re using a version of the Embrace SDK that supports Performance Tracing.
  2. (Optional) Enable API desugaring for your app if you want users running Android 5.x and 6.x to report traces.
  3. Instrument your app using the reference guide in this section to start adding traces to your operations, or refer to the API docs for a more comprehensive description of the public API.
  4. See the traces in the Traces section of the Embrace dashboard.

API Usage Examples

Create a Span

// create a trace by creating its root span
// recording will not begin until the span has been started
val activityLoad = Embrace.getInstance().createSpan("load-activity")

Create and Start Span Atomically

// activityLoad will either be a span that has already started or null if 
// the creation or start attempt was unsuccessful
val activityLoad = Embrace.getInstance().startSpan("load-activity")

Start Span That Tracks an Operation That Started at an Earlier Time

val appStartTimeMillis = getAppStartTime()
val appLaunchTrace = Embrace.getInstance().createSpan("app-launch")

// begin recording a trace that has a different start time than
// the current time by starting its root span with a specific timestamp
appLaunchTrace?.start(startTimeMs = appStartTimeMillis)

Add Attributes and Span Events

val embrace = Embrace.getInstance()
val activityLoad = embrace.startSpan("load-activity")
val imageLoad = activityLoad?.let { embrace.startSpan("load-image", this) }

val image = fetchImage()

// record important event at point in time
imageLoad?.addEvent("network-request-finished")

// record attribute particular to this span instance
imageLoad?.addAttribute("image-name", image.name)

Stop Span For Operation That Ended Earlier

val activityLoad = Embrace.getInstance().startSpan("load-activity")

// some time passes after the operation being time has finished

activityLoad?.stop(endTimeMs = getActualEndTimeMilllis())

Stop Span For an Operation That Failed

val activityLoad = Embrace.getInstance().startSpan("load-activity")

try {
loadActivity()
} catch (e: IllegalStateException) {
activityLoad?.addAttribute("error-message", getErrorMessage(e))
activityLoad?.stop(ErrorCode.FAILURE)
} finally {
// calling stop on an already-stopped span will not change its state
activityLoad?.stop()
}

Add a Child Span If the Parent Started Properly

val embrace = Embrace.getInstance()
val activityLoad = embrace.startSpan("load-activity")

// create and start a child span if activityLoad is created and started successfully
val imageLoad = activityLoad?.let { embrace.startSpan("load-image", it) }

Record a Trace Before the Embrace SDK Has Started

// record a span based on start and end times that are in the past
Embrace.getInstance().recordCompletedSpan(
name = "activity-create",
startTimeMs = startTimeMillis,
endTimeMs = endTimeMillis
)

Get a Reference to an In-Progress Span

val embrace = Embrace.getInstance()
val activityLoad = embrace.startSpan("load-activity")
val activityLoadSpanId = activityLoad?.spanId

/* some other part of the code without access to activityLoad */

embrace.getSpan(activityLoadSpanId)?.stop()

Export to OpenTelemetry Collectors

To send telemetry to any OpenTelemetry Collector directly from the app, SpanExporter and LogRecordExporter can be used to do that. When configured, telemetry will be sent to these exporters as soon as they are recorded. More than one exporter of each signal can be configured, but be aware of the performance impact of sending too many network requests if that is applicable.

Embrace.getInstance().addSpanExporter(mySpanExporter)
Embrace.getInstance().addLogRecordExporter(myLogExporter)
info

Please note that exporters must be configured before the Embrace SDK is started. Exporters added after the SDK has already been started will not be used.

Local Testing

To see this working locally, LoggingSpanExporter and SystemOutLogRecordExporter can be used to output to logcat.

2024-03-05 14:15:15.342 29672-29756 LoggingSpanExporter     io.embrace.mysampleapp          I  'emb-startup-moment' : d38b4ac26baf1a862ed4a028af7d08ac e3e82dd0f86c0eed INTERNAL [tracer: io.embrace.android.embracesdk:=6.9.1] AttributesMap{data={emb.sequence_id=4, emb.type=PERFORMANCE, emb.key=true}, capacity=128, totalAddedValues=3}

Sending Telemetry Off the Device

You can send your data to any generic OpenTelemetry Collector by using any Android-compatible exporter. Note that not all Java SpanExporter or LogRecordExporter can be used on Android.

warning

Network request to OpenTelemetry Collectors should not be logged

To prevent an infinite loop of network requests spans, any requests used to export telemetry to OpenTelemetry Collectors should be excluded from being recorded by the Embrace SDK using the disable_url_patterns setting in the Embrace Configuration file. See this page for details.

 //GRPC through an OTel Collector in a local docker image
val customDockerExporter = OtlpGrpcSpanExporter.builder()
.setEndpoint("https://otel-collector.mydomain.com:4317")
.build()

Embrace.getInstance().addSpanExporter(customDockerExporter)

Sending Telemetry to Grafana Cloud

To send telemetry to Grafana Cloud, set up the collector and add an authorization token as a header.

//HTTPS to an OTEL Collector in Grafana Cloud
val grafanaCloudExporter = OtlpHttpSpanExporter.builder()
.setEndpoint("https://myinstance.grafana.net/otlp/v1/traces")
.addHeader("Authorization", "YourToken")
.build()

Embrace.getInstance().addSpanExporter(grafanaCloudExporter)