Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add composition tracing example #263

Merged
merged 8 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Scroll List With Composition Tracing" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests">
<module name="macrobenchmark.macrobenchmark.main" />
<option name="TESTING_TYPE" value="3" />
<option name="METHOD_NAME" value="scrollComposeList" />
<option name="CLASS_NAME" value="com.example.macrobenchmark.benchmark.frames.FrameTimingBenchmark" />
<option name="PACKAGE_NAME" value="" />
<option name="TEST_NAME_REGEX" value="" />
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
<option name="EXTRA_OPTIONS" value="-e androidx.benchmark.perfettoSdkTracing.enable true" />
<option name="RETENTION_ENABLED" value="No" />
<option name="RETENTION_MAX_SNAPSHOTS" value="2" />
<option name="RETENTION_COMPRESS_SNAPSHOTS" value="false" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
<option name="DEBUGGER_TYPE" value="Auto" />
<Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Auto>
<Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Hybrid>
<Java>
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Java>
<Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Native>
<Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
<option name="STARTUP_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Java/Kotlin Method Sample (legacy)" />
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
</Profilers>
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
25 changes: 25 additions & 0 deletions MacrobenchmarkSample/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,31 @@ 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommend an empty line between startup and frame timing benchmarks. They're hard to distinguish as they are already.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no startup benchmarks 🤔 . I can make a line between the custom traces and frame timing results (just copied it from the outputs).

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).
Expand Down
1 change: 1 addition & 0 deletions MacrobenchmarkSample/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -90,23 +91,25 @@ 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()
AlertDialog
.Builder(this@ComposeActivity)
.setMessage("Item clicked")
.show()
})
}
)
}
}
}
Expand All @@ -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(
Expand Down
21 changes: 13 additions & 8 deletions MacrobenchmarkSample/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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" }
Expand All @@ -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" }
Expand All @@ -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" }
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions MacrobenchmarkSample/macrobenchmark/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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)) }
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading