Capture Macrobenchmark metrics

Metrics are the main type of information extracted from your benchmarks. They are passed to the measureRepeated function as a List, which lets you specify multiple measured metrics at once. At least one type of metric is required for the benchmark to run.

The following code snippet captures frame timing and custom trace section metrics for a Jetpack Compose lazy layout interface:

@OptIn(ExperimentalMetricApi::class)
    @Test
    fun scrollComposeList() {
        benchmarkRule.measureRepeated(
            // [START_EXCLUDE]
            packageName = TARGET_PACKAGE,
            metrics = listOf(
                FrameTimingMetric(),
                // Measure power usage. This is supported on Pixel 6 and later.
                PowerMetric(PowerMetric.Type.Power(
                    mapOf(
                        PowerCategory.CPU to PowerCategoryDisplayLevel.TOTAL,
                        PowerCategory.DISPLAY to PowerCategoryDisplayLevel.TOTAL,
                        PowerCategory.GPU to PowerCategoryDisplayLevel.TOTAL,
                        PowerCategory.NETWORK to PowerCategoryDisplayLevel.TOTAL,
                    )
                )),
                // Measure custom trace sections by name EntryRow (which is added to the EntryRow composable).
                // Mode.Sum measures combined duration and also how many times it occurred in the trace.
                // This way, you can estimate whether a composable recomposes more than it should.
                TraceSectionMetric("EntryRowCustomTrace", TraceSectionMetric.Mode.Sum),
                // This trace section takes into account the SQL wildcard character %,
                // which can find trace sections without the full name.
                // This way, you can measure composables produced by the composition tracing
                // and measure how long they took and how many times they recomposed.
                // WARNING: This metric only shows results when running with composition tracing, otherwise it won't be visible in the outputs.
                TraceSectionMetric("%EntryRow%", TraceSectionMetric.Mode.Sum),
            ),
            // Try switching to different compilation modes to see the effect
            // it has on frame timing metrics.
            compilationMode = CompilationMode.None(),
            startupMode = StartupMode.WARM, // restarts activity each iteration
            iterations = DEFAULT_ITERATIONS,
            // [END_EXCLUDE]
            setupBlock = {
                uiAutomator {
                    // Before starting to measure, navigate to the UI to be measured.
                    startIntent(Intent("$packageName.COMPOSE_ACTIVITY"))
                }
            }
        ) {
            uiAutomator {
                onElement { isScrollable }.fling(Direction.DOWN)
            }
        }
    }

In the following example, EntryRowCustomTrace represents a custom trace section defined inside the composable item layers using the standard Kotlin trace(sectionName) { ... } block wrapper. To provide data for TraceSectionMetric, you must wrap the target UI components inside your application's production codebase with the standard Jetpack runtime trace block wrapper:

@Composable
private fun EntryRow(entry: Entry, modifier: Modifier = Modifier) = trace("EntryRowCustomTrace") {
    Card(modifier = modifier) {
        Row(verticalAlignment = Alignment.CenterVertically) {
            Text(
                text = entry.contents,
                modifier = Modifier
                    .padding(16.dp)
                    .wrapContentSize()
            )

            Spacer(modifier = Modifier.weight(1f))

            Checkbox(
                checked = false,
                onCheckedChange = {},
                modifier = Modifier.padding(16.dp)
            )
        }
    }
}

Benchmark results are output directly to the Benchmark terminal tab inside Android Studio, as shown in Figure 1. If multiple metrics are defined, all their computed data points are combined in the summary window.

Results of TraceSectionMetric and FrameTimingMetric.
Figure 1. Combined console results of TraceSectionMetric and FrameTimingMetric for a modern Compose layout.

StartupTimingMetric, FrameTimingMetric, TraceSectionMetric, and PowerMetric are covered in detail below. For a full list of available benchmark metrics, see the subclasses of Metric in the API reference.

StartupTimingMetric

StartupTimingMetric captures app startup timing metrics with the following values:

  • timeToInitialDisplayMs: The amount of time from when the system receives a launch intent to when it renders the first frame of the destination screen.
  • timeToFullDisplayMs: The amount of time from when the system receives a launch intent to when the app reports fully drawn using the internal platform reporting mechanisms. The measurement stops at the completion of rendering the first frame after—or containing—the fully drawn signal.

StartupTimingMetric outputs the minimum, median, and maximum values from the startup iterations. To assess startup improvement, always focus on median values, since they provide the best estimate of typical user startup times.

In a Compose-first architecture, don't attempt to invoke activity.reportFullyDrawn manually. Instead, use the Compose-safe asynchronous utilities ReportDrawn, ReportDrawnWhen, or ReportDrawnAfter inside your screen composables to automatically signal to Macrobenchmark when your async network data or complex UI states have finished rendering.

For more information about analyzing and optimizing initialization performance, see App startup time.

FrameTimingMetric

FrameTimingMetric captures precise timing information from frames produced by a benchmark journey, such as scrolling a list or a complex UI layout animation, and outputs the following diagnostic values:

  • frameOverrunMs: the amount of time a given frame misses its deadline by. Positive numbers indicate a dropped frame accompanied by visible jank or stutter. Negative numbers indicate how much faster a frame completed relative to the subsystem hardware deadline. Note: This metric is available only on Android 12 (API level 31) and later.
  • frameDurationCpuMs: the amount of time the frame spent actively being produced on the CPU across both the main application UI thread and the Compose RenderThread.

These measurements are collected in a distribution of 50th, 90th, 95th, and 99th percentiles:

frameDurationCpuMs P50 3.5, P90 6.0, P95 6.4, P99 11.0
frameOverrunMs P50 -11.6, P90 -7.2, P95 -7.1, P99 -1.2

When optimizing Jetpack Compose layout hierarchies, look at your worst-performing frames (the P95 and P99 bounds). If frameOverrunMs spikes into positive integers at the high percentiles, it indicates that recompositions are stalling the main thread during heavy scroll animations.

For deeper insights into identifying and resolving slow frames, see Jetpack Compose Performance.

TraceSectionMetric

TraceSectionMetric captures the number of times a specific trace section occurs and the absolute amount of time it takes to execute. For time tracking, it outputs the minimum, median, and maximum times in milliseconds. The target trace section is defined either by the function call trace(sectionName) or the lower-level block boundaries between Trace.beginSection(sectionName) and Trace.endSection() or their async variants.

EntryRowCustomTraceCount min 20.0, median 28.0, max 50.0
EntryRowCustomTraceSumMs min 34.9, median 44.4, max 66.6

By default, the metric only outputs trace sections compiled directly from your own application package binaries. To include processes originating from outside your app's package boundary, set the property targetPackageOnly = false.

When working on Jetpack Compose Runtime Tracing, you can surface individual composable functions in your system trace graphs without writing manual trace wrappers by enabling composition tracing.

While adding the androidx.compose.runtime:runtime-tracing dependency to your target application is sufficient for manual profiler traces, capturing these traces programmatically within a Macrobenchmark run requires additional configuration inside your benchmark module.

For complete setup instructions, see Capture a trace with Jetpack Macrobenchmark.

PowerMetric

PowerMetric captures the change in power or energy over the duration of your Macrobenchmark run. Each selected category is broken down into its measurable hardware components, while unselected categories are grouped into an "unselected" bucket.

Hardware Requirement: These metrics measure system-wide consumption rather than per-app calculations. Consequently, data collection is limited to physical Google Pixel 6, Pixel 6 Pro, and newer physical devices.

The metric outputs two measurements per category:

  • power<category>Uw: the amount of power consumed over the duration of your test in this category (measured in microwatts).
  • energy<category>Uws: the total amount of energy transferred per unit of time for the duration of your test in this category (measured in microwatt-seconds).

Categories include the following:

  • CPU
  • DISPLAY
  • GPU
  • GPS
  • MEMORY
  • MACHINE_LEARNING
  • NETWORK
  • UNCATEGORIZED

With some categories, like CPU, it might be difficult to separate work done by other processes from work done by your own app. To minimize the interference, remove or restrict unnecessary apps and accounts.

powerCategoryCpuUw min 300.2, median 346.1, max 519.6
powerCategoryDisplayUw min 319.8, median 325.8, max 329.7
powerCategoryGpuUw min 18.8, median 23.3, max 36.9
powerCategoryNetworkUw min 97.3, median 123.3, max 681.3
powerTotalUw min 1234.8, median 1316.6, max 2112.4
powerUnselectedUw       min  483.3,  median  512.6,  max  561.7

Analyzing core subsystems

PowerMetric captures the change in power or energy over the duration of your test for the provided power categories. Each category you select is broken down into its measurable subcomponents, and unselected categories are added to the "unselected" metric.

The terminal outputs map to the configuration you request:

  • powerCategoryCpuUw: The amount of power consumed by the CPU over the duration of your test.
  • powerCategoryGpuUw: The amount of power consumed by the GPU over the duration of your test.
  • powerUnselectedUw: The aggregate power consumed by all available hardware categories that were not explicitly requested in your initialization map.

To prevent erratic data spikes on the hardware rails during a run, lock screen brightness to a fixed value, maintain a stable device temperature, and close competing background processes before starting the Macrobenchmark loop.

Additional resources

Views content