diff --git a/MacrobenchmarkSample/.run/Scroll List With Composition Tracing.run.xml b/MacrobenchmarkSample/.run/Scroll List With Composition Tracing.run.xml new file mode 100644 index 00000000..3e1a477d --- /dev/null +++ b/MacrobenchmarkSample/.run/Scroll List With Composition Tracing.run.xml @@ -0,0 +1,64 @@ + + + + + \ No newline at end of file diff --git a/MacrobenchmarkSample/README.md b/MacrobenchmarkSample/README.md index dd365e6c..7a7c2441 100644 --- a/MacrobenchmarkSample/README.md +++ b/MacrobenchmarkSample/README.md @@ -38,6 +38,32 @@ Alternatively, run the benchmarks from terminal with: ./gradlew macrobenchmark:cC ``` +### Macrobenchmark with Composition Tracing +Composition Tracing allows to run system tracing with information on when all Composables (re)compose. +This gives you insights on where the UI spends majority of the time and helps you find jank. + +To set up composition tracing for your app, follow our [documentation](https://developer.android.com/jetpack/compose/tooling/tracing). +To get composition tracing when running a macrobenchmark, you also need to use `androidx.benchmark.perfettoSdkTracing.enable=true` instrumentation argument. + +You can check the `Scroll List With Composition Tracing` run configuration that is part of the project, +which runs the scroll compose list benchmark while also recording the information on composition. + +It produces results like in the following table: +``` +FrameTimingBenchmark_scrollComposeList +%EntryRow (%Count min 5.0, median 6.0, max 6.0 +%EntryRow (%Ms min 10.2, median 11.8, max 16.2 +EntryRowCustomTraceCount min 5.0, median 6.0, max 6.0 +EntryRowCustomTraceMs min 10.0, median 11.7, max 16.1 + +frameDurationCpuMs P50 4.8, P90 6.8, P95 8.9, P99 15.3 +frameOverrunMs P50 -9.2, P90 -1.9, P95 266.9, P99 310.9 +Traces: Iteration 0 1 2 3 4 5 6 7 8 9 +``` + +And from there you can also delve into the system trace, which shows information on composition: +![System trace with composition tracing](media/composition-tracing.png) + ### Reporting Issues You can report an [Issue with the sample](https://github.com/googlesamples/android-performance/issues) using this repository. If you find an issue with the Macrobenchmark library, report it using the [Issue Tracker](https://issuetracker.google.com/issues/new?component=975669&template=1519452). diff --git a/MacrobenchmarkSample/app/build.gradle.kts b/MacrobenchmarkSample/app/build.gradle.kts index 9b8610b5..88f91c42 100644 --- a/MacrobenchmarkSample/app/build.gradle.kts +++ b/MacrobenchmarkSample/app/build.gradle.kts @@ -89,6 +89,7 @@ dependencies { implementation(libs.compose.material) implementation(libs.compose.ui) implementation(libs.compose.ui.tooling) + implementation(libs.compose.runtime.tracing) implementation(libs.constraintlayout) implementation(libs.concurrentfutures) implementation(libs.core) diff --git a/MacrobenchmarkSample/app/src/main/java/com/example/macrobenchmark/target/activity/clicklatency/ComposeActivity.kt b/MacrobenchmarkSample/app/src/main/java/com/example/macrobenchmark/target/activity/clicklatency/ComposeActivity.kt index efabda6b..cbef2597 100644 --- a/MacrobenchmarkSample/app/src/main/java/com/example/macrobenchmark/target/activity/clicklatency/ComposeActivity.kt +++ b/MacrobenchmarkSample/app/src/main/java/com/example/macrobenchmark/target/activity/clicklatency/ComposeActivity.kt @@ -48,6 +48,7 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.tracing.trace import com.example.macrobenchmark.target.recyclerview.Entry import com.example.macrobenchmark.target.util.ClickTrace @@ -90,15 +91,16 @@ class ComposeActivity : ComponentActivity() { value = value, onValueChange = { value = it }, placeholder = { Text("Enter text here") } - ) + ) LazyColumn( modifier = Modifier .testTag("myLazyColumn") ) { items(data, key = { it.contents }) { item -> - EntryRow(entry = item, - Modifier + EntryRow( + entry = item, + modifier = Modifier .padding(8.dp) .clickable { ClickTrace.onClickPerformed() @@ -106,7 +108,8 @@ class ComposeActivity : ComponentActivity() { .Builder(this@ComposeActivity) .setMessage("Item clicked") .show() - }) + } + ) } } } @@ -124,7 +127,7 @@ class ComposeActivity : ComponentActivity() { } @Composable -private fun EntryRow(entry: Entry, modifier: Modifier = Modifier) { +private fun EntryRow(entry: Entry, modifier: Modifier = Modifier) = trace("EntryRowCustomTrace") { Card(modifier = modifier) { Row(verticalAlignment = Alignment.CenterVertically) { Text( diff --git a/MacrobenchmarkSample/gradle/libs.versions.toml b/MacrobenchmarkSample/gradle/libs.versions.toml index a543ac9c..978fed55 100644 --- a/MacrobenchmarkSample/gradle/libs.versions.toml +++ b/MacrobenchmarkSample/gradle/libs.versions.toml @@ -1,10 +1,10 @@ [versions] -agp = "8.1.1" +agp = "8.1.4" activity = "1.7.2" appcompat = "1.6.1" -benchmark = "1.2.0-rc01" -composeBom = "2023.09.02" -composeCompiler = "1.4.7" +benchmark = "1.2.1" +composeBom = "2023.10.01" +composeCompiler = "1.5.4" constraintLayout = "2.1.4" core = "1.12.0" coroutines = "1.6.4" @@ -13,13 +13,15 @@ curtains = "1.2.4" dataStore = "1.0.0" espressoCore = "3.5.1" jUnit = "1.1.5" -kotlin = "1.8.21" +kotlin = "1.9.20" lifecycle = "2.6.2" material = "1.9.0" profileInstaller = "1.3.1" rules = "1.5.0" -tracing = "1.1.0" -uiAutomator = "2.3.0-alpha04" +runtimeTracing = "1.0.0-alpha05" +tracing = "1.3.0-alpha02" +tracingPerfetto = "1.0.0" +uiAutomator = "2.3.0-alpha05" [libraries] androidx-rules = { module = "androidx.test:rules", version.ref = "rules" } @@ -30,6 +32,7 @@ appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "a benchmark-junit = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "benchmark" } compose-activity = { group = "androidx.activity", name = "activity-compose", version.ref = "activity" } compose-material = { group = "androidx.compose.material", name = "material" } +compose-runtime-tracing = { module = "androidx.compose.runtime:runtime-tracing", version.ref = "runtimeTracing" } compose-ui = { group = "androidx.compose.ui", name = "ui" } compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } concurrentfutures = { group = "androidx.concurrent", name = "concurrent-futures-ktx", version.ref = "concurrentFutures" } @@ -44,13 +47,15 @@ lifecycle = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", vers profileinstaller = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "profileInstaller" } squareup-curtains = { group = "com.squareup.curtains", name = "curtains", version.ref = "curtains" } tracing = { group = "androidx.tracing", name = "tracing-ktx", version.ref = "tracing" } +tracing-perfetto = { module = "androidx.tracing:tracing-perfetto", version.ref = "tracingPerfetto" } +tracing-perfetto-binary = { module = "androidx.tracing:tracing-perfetto-binary", version.ref = "tracingPerfetto" } ui-automator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiAutomator" } viewmodel = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycle" } [plugins] application = { id = "com.android.application", version.ref = "agp" } -baselineprofile = { id = "androidx.baselineprofile", version.ref = "benchmark"} +baselineprofile = { id = "androidx.baselineprofile", version.ref = "benchmark" } library = { id = "com.android.library", version.ref = "agp" } test = { id = "com.android.test", version.ref = "agp" } kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } diff --git a/MacrobenchmarkSample/gradle/wrapper/gradle-wrapper.properties b/MacrobenchmarkSample/gradle/wrapper/gradle-wrapper.properties index 755af426..83bd9a65 100644 --- a/MacrobenchmarkSample/gradle/wrapper/gradle-wrapper.properties +++ b/MacrobenchmarkSample/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Mon Mar 06 15:40:15 GMT 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/MacrobenchmarkSample/macrobenchmark/build.gradle.kts b/MacrobenchmarkSample/macrobenchmark/build.gradle.kts index 47febc58..f6c7f5f3 100644 --- a/MacrobenchmarkSample/macrobenchmark/build.gradle.kts +++ b/MacrobenchmarkSample/macrobenchmark/build.gradle.kts @@ -91,4 +91,10 @@ dependencies { implementation(libs.androidx.junit) implementation(libs.espresso) implementation(libs.ui.automator) + + // Adds dependencies to enable running Composition Tracing from Macrobenchmarks. + // These dependencies are already included with Macrobenchmark, but we have them here to specify the version. + // For more information on Composition Tracing, check https://developer.android.com/jetpack/compose/tooling/tracing. + implementation(libs.tracing.perfetto) + implementation(libs.tracing.perfetto.binary) } diff --git a/MacrobenchmarkSample/macrobenchmark/src/main/java/com/example/macrobenchmark/benchmark/frames/FrameTimingBenchmark.kt b/MacrobenchmarkSample/macrobenchmark/src/main/java/com/example/macrobenchmark/benchmark/frames/FrameTimingBenchmark.kt index 4f032aae..74abacf9 100644 --- a/MacrobenchmarkSample/macrobenchmark/src/main/java/com/example/macrobenchmark/benchmark/frames/FrameTimingBenchmark.kt +++ b/MacrobenchmarkSample/macrobenchmark/src/main/java/com/example/macrobenchmark/benchmark/frames/FrameTimingBenchmark.kt @@ -17,9 +17,12 @@ package com.example.macrobenchmark.benchmark.frames import android.content.Intent +import android.graphics.Point import androidx.benchmark.macro.CompilationMode +import androidx.benchmark.macro.ExperimentalMetricApi import androidx.benchmark.macro.FrameTimingMetric import androidx.benchmark.macro.StartupMode +import androidx.benchmark.macro.TraceSectionMetric import androidx.benchmark.macro.junit4.MacrobenchmarkRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest @@ -67,12 +70,25 @@ class FrameTimingBenchmark { } // [END macrobenchmark_control_your_app] + @OptIn(ExperimentalMetricApi::class) @Test fun scrollComposeList() { benchmarkRule.measureRepeated( // [START_EXCLUDE] packageName = TARGET_PACKAGE, - metrics = listOf(FrameTimingMetric()), + metrics = listOf( + FrameTimingMetric(), + // Measure custom trace sections by name EntryRow (which is added to the EntryRow composable). + // Mode.Sum measure 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 knowing 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(), @@ -100,7 +116,7 @@ class FrameTimingBenchmark { column.setGestureMargin(device.displayWidth / 5) // Scroll down several times - repeat(3) { column.fling(Direction.DOWN) } + repeat(1) { column.drag(Point(column.visibleCenter.x, column.visibleBounds.top)) } } } } diff --git a/MacrobenchmarkSample/media/composition-tracing.png b/MacrobenchmarkSample/media/composition-tracing.png new file mode 100644 index 00000000..6a51f949 Binary files /dev/null and b/MacrobenchmarkSample/media/composition-tracing.png differ