Skip to content

Commit

Permalink
Merge pull request #263 from android/tm/composition-tracing
Browse files Browse the repository at this point in the history
Add composition tracing example
  • Loading branch information
mlykotom authored Nov 27, 2023
2 parents e091186 + ded1e1c commit f57d365
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 16 deletions.
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>
26 changes: 26 additions & 0 deletions MacrobenchmarkSample/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
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.

0 comments on commit f57d365

Please sign in to comment.