Skip to content

Commit 70cc79b

Browse files
Merge pull request #719 from square/sedwards/update-benchmarks-readme
Add Baseline and Perf Tests to Perf Poetry
2 parents 15bbeea + dac8999 commit 70cc79b

File tree

30 files changed

+1133
-72
lines changed

30 files changed

+1133
-72
lines changed

.github/workflows/kotlin.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ jobs:
123123
api-level: ${{ matrix.api-level }}
124124
arch: x86_64
125125
# Skip the benchmarks as this is running on emulators
126-
script: ./gradlew connectedCheck -x :benchmarks:dungeon-benchmark:connectedReleaseAndroidTest --no-daemon --stacktrace
126+
script: ./gradlew connectedCheck -x :benchmarks:dungeon-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-benchmark:connectedCheck --no-daemon --stacktrace
127127

128128
- name: Upload results
129129
if: ${{ always() }}

RELEASING.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
1. Confirm that the kotlin build is green before committing any changes
99
(Note we exclude benchmarks, but you can check those too!)
1010
```bash
11-
./gradlew build && ./gradlew connectedCheck -x :benchmarks:dungeon-benchmark:connectedCheck
11+
./gradlew build && ./gradlew connectedCheck -x :benchmarks:dungeon-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-benchmark:connectedCheck
1212
```
1313

1414
1. Update your tags.

benchmarks/README.md

+65-16
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
11
# benchmarks
22

3-
This module contains benchmarks. Used to measure and help improve Workflow performance. These tests
4-
should be run on physical devices.
3+
This module contains benchmarks. Used to measure and help improve Workflow performance. The
4+
deterministic tests - such as those for render passes - can be run on any device (virtual or
5+
otherwise), but the benchmarks should be run on physical devices, or the closest approximation
6+
to physical devices you can get.
57

6-
## dungeon-benchmark
8+
## Baseline Profiles
9+
10+
The sample apps can be used to extract 'baseline profiles' to improve code loading time after first
11+
install. See [baseline profiles](https://developer.android.com/studio/profile/baselineprofiles).
12+
13+
We can use
14+
[dungeon](dungeon-benchmark/src/main/java/com/squareup/sample/dungeon/benchmark/DungeonGatherBaselineProfile.kt)
15+
or [performance-poetry](performance-poetry/complex-benchmark/src/main/java/com/squareup/benchmarks/performance/poetry/complex/benchmark/ComplexPoetryGatherBaseline.kt)
16+
to gather profiles. Performance Poetry has more adaptability and better benchmarks at this time, so
17+
focus on that.
718

8-
This is for the /samples/dungeon/app. This includes a macrobenchmark
9-
[test](dungeon-benchmark/src/main/java/com/squareup/sample/dungeon/benchmark/WorkflowBaselineProfiles.kt)
10-
that exercises this sample app (with UiAutomator) to collect
11-
a [baseline profile](https://developer.android.com/studio/profile/baselineprofiles). After running
12-
this the `profile.txt` file can be taken off of the device and used directly in the /src/main
13-
directory of the application. This will include the profile into the APK for guided optimization at
14-
install time.
19+
After extracting the profile with one of those tests, the `profile.txt` file can be taken off of the
20+
device and used directly in the /src/main directory of the application. This will include the
21+
profile into the APK for guided optimization at install time.
1522

1623
Better yet, the profile can be split up and added into each Workflow module's src directory so that
1724
it will be included with all APKs built using Workflow (including 3rd party). To do this a java
18-
program available in the android-x open source [code](https://github.com/androidx/androidx) is used
19-
to split the profile based on src paths. That tool is found at
25+
program, available in the Android-X open source [code](https://github.com/androidx/androidx), is
26+
used to split the profile based on src paths. That tool is found at
2027
[androidx-main/frameworks/support/out/androidx/development/splitBaselineProfiles].
2128

2229
To split the profile paths for Workflow, the tool can be used as follows:
@@ -29,7 +36,22 @@ This will create an output file separated by module and then also by package as
2936
profile for each module can be added into its /src/main directory as `baseline-prof.txt`. Then on a
3037
release build this will be included with the resulting APK/binary.
3138

32-
The other [test](dungeon-benchmark/src/main/java/com/squareup/sample/dungeon/benchmark/WorkflowBaselineBenchmark.kt)
39+
## dungeon-benchmark
40+
41+
These are benchmarks for the [../samples/dungeon] app. Please instead use performance-poetry where
42+
possible.
43+
44+
*PLEASE NOTE:* The dungeon app includes AI ghosts with random travel which can 'eat' the player,
45+
ending the game with a dialog. This obviously makes these test non-deterministic. We used these
46+
originally to exercise enough of Workflow for a baseline profile. We can avoid at least the dialog
47+
popping to end the game by making the player survive with the following change to
48+
[../samples/dungeon/common/src/main/java/com/squareup/sample/dungeon/Game.kt]:
49+
50+
```kotlin
51+
val isPlayerEaten: Boolean get() = false // Never Eaten!
52+
```
53+
54+
The [benchmark](dungeon-benchmark/src/main/java/com/squareup/sample/dungeon/benchmark/DungeonStartupBenchmark.kt)
3355
is used to verify the results of including the baseline profiles on the startup time. This runs the
3456
same scenario with and without forcing the use of the profiles. To force the use of profiles, the
3557
`libs.androidx.profileinstaller` dependency is included into the app under profile (dungeon in this
@@ -41,11 +63,38 @@ Module of code for performance testing related to poetry applications.
4163

4264
### complex-poetry
4365

44-
This application is a modification of the samples/containers/app-poetry app which uses also the
66+
This application is a modified version of the samples/containers/app-poetry app which also uses the
4567
common components in samples/containers/common and samples/containers/poetry. It modifies this
4668
application to pass the Workflow
47-
a [SimulatedPerfConfig](performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/poetry/SimulatedPerfConfig.kt).
69+
a [SimulatedPerfConfig.](performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/SimulatedPerfConfig.kt)
4870

4971
In this case we specify that the app should be more 'complex' which adds delays into each of the
5072
selections that are run by Worker's which then trigger a loading state that is handled by the
51-
[MaybeLoadingGatekeeperWorkflow](performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/poetry/MaybeLoadingGatekeeperWorkflow.kt).
73+
[MaybeLoadingGatekeeperWorkflow.](performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/MaybeLoadingGatekeeperWorkflow.kt)
74+
75+
One benchmark that is included (and run in CI) as a UI test is the [RenderPassTest.](performance-poetry/complex-poetry/src/androidTest/java/com/squareup/benchmarks/performance/complex/poetry/RenderPassTest.kt)
76+
This measures two things:
77+
78+
1. The number of "Render Passes" that the Raven scenario triggers.
79+
1. The ratio of 'fresh renderings' to 'stale renderings' in the Raven scenario.
80+
81+
A rendering is 'fresh' if the node's state has changed. A rendering is 'stale' if its state is the
82+
same. Note that Workflow is designed to to have cheap, idempotent renderings and the fresh rendering
83+
ratio will never be 1.0 by design. However, if there are smells that have led to 'render churn' one
84+
will see a very poor rendering ratio and we would like a way to track that and to test that it is
85+
constant (or improving!).
86+
87+
This module includes an [instrumentation package](performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation)
88+
that has two [WorkflowInterceptor]s that can count render passes or instrument perfetto Trace
89+
sections, as well as data class for tracking those.
90+
91+
This module also includes a [robots package](performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/robots)
92+
that provides some utility helper 'robots' for the UiAutomator [androidx.test.uiautomator.UiDevice]
93+
as well as scenarios specific to this Complex Poetry application.
94+
95+
### complex-benchmark
96+
97+
This is an Android Test module which hosts an application that can run androidx.macrobenchmarks.
98+
See the kdoc on [ComplexPoetryBenchmarks.](performance-poetry/complex-benchmark/src/main/java/com/squareup/benchmarks/performance/complex/poetry/benchmark/ComplexPoetryBenchmarks.kt)
99+
100+
The results for this are stored in the same folder at [ComplexPoetryResults.txt.](performance-poetry/complex-benchmark/src/main/java/com/squareup/benchmarks/performance/complex/poetry/benchmark/ComplexPoetryResults.txt)
+8-11
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,21 @@ import kotlin.time.ExperimentalTime
2323
*/
2424
@RunWith(AndroidJUnit4::class)
2525
@OptIn(ExperimentalBaselineProfilesApi::class)
26-
public class WorkflowBaselineProfiles {
26+
class DungeonGatherBaselineProfile {
2727

28-
@get:Rule
29-
public val baselineProfileRule: BaselineProfileRule = BaselineProfileRule()
28+
@get:Rule val baselineProfileRule: BaselineProfileRule = BaselineProfileRule()
3029

3130
private lateinit var context: Context
3231
private lateinit var device: UiDevice
3332

34-
@Before
35-
public fun setUp() {
33+
@Before fun setUp() {
3634
val instrumentation = InstrumentationRegistry.getInstrumentation()
3735
context = ApplicationProvider.getApplicationContext()
3836
device = UiDevice.getInstance(instrumentation)
3937
}
4038

4139
@Test
42-
@OptIn(ExperimentalTime::class)
43-
public fun baselineProfiles() {
40+
@OptIn(ExperimentalTime::class) fun baselineProfiles() {
4441
baselineProfileRule.collectBaselineProfile(
4542
packageName = PACKAGE_NAME,
4643
) {
@@ -50,9 +47,9 @@ public class WorkflowBaselineProfiles {
5047
}
5148
}
5249

53-
public companion object {
50+
companion object {
5451

55-
public fun openMazeAndNavigate(device: UiDevice) {
52+
fun openMazeAndNavigate(device: UiDevice) {
5653
val boardsList =
5754
UiScrollable(UiSelector().className("androidx.recyclerview.widget.RecyclerView"))
5855
boardsList.waitForExists(UI_TIMEOUT_MS)
@@ -70,7 +67,7 @@ public class WorkflowBaselineProfiles {
7067
}
7168
}
7269

73-
public const val PACKAGE_NAME: String = "com.squareup.sample.dungeon"
74-
public const val UI_TIMEOUT_MS: Long = 2000L
70+
const val PACKAGE_NAME: String = "com.squareup.sample.dungeon"
71+
const val UI_TIMEOUT_MS: Long = 2000L
7572
}
7673
}
+15-17
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@ package com.squareup.sample.dungeon.benchmark
33
import android.content.Context
44
import androidx.benchmark.macro.BaselineProfileMode
55
import androidx.benchmark.macro.CompilationMode
6-
import androidx.benchmark.macro.FrameTimingMetric
76
import androidx.benchmark.macro.StartupMode
87
import androidx.benchmark.macro.StartupTimingMetric
98
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
109
import androidx.test.core.app.ApplicationProvider
1110
import androidx.test.ext.junit.runners.AndroidJUnit4
1211
import androidx.test.platform.app.InstrumentationRegistry
1312
import androidx.test.uiautomator.UiDevice
14-
import com.squareup.sample.dungeon.benchmark.WorkflowBaselineProfiles.Companion.PACKAGE_NAME
13+
import com.squareup.sample.dungeon.benchmark.DungeonGatherBaselineProfile.Companion.PACKAGE_NAME
1514
import org.junit.Before
1615
import org.junit.Rule
1716
import org.junit.Test
@@ -25,36 +24,35 @@ import kotlin.time.ExperimentalTime
2524
* This test compares performance with and without profiles.
2625
*/
2726
@RunWith(AndroidJUnit4::class)
28-
public class WorkflowBaselineBenchmark {
27+
class DungeonStartupBenchmark {
2928

30-
@get:Rule
31-
public val benchmarkRule: MacrobenchmarkRule = MacrobenchmarkRule()
29+
@get:Rule val benchmarkRule: MacrobenchmarkRule = MacrobenchmarkRule()
3230

3331
private lateinit var context: Context
3432
private lateinit var device: UiDevice
3533

36-
@Before
37-
public fun setUp() {
34+
@Before fun setUp() {
3835
val instrumentation = InstrumentationRegistry.getInstrumentation()
3936
context = ApplicationProvider.getApplicationContext()
4037
device = UiDevice.getInstance(instrumentation)
4138
}
4239

43-
@Test
44-
public fun benchmarkNoCompilation() {
45-
benchmark(CompilationMode.None())
40+
@Test fun benchmarkStartupNoCompilation() {
41+
benchmarkStartup(CompilationMode.None())
4642
}
4743

48-
@Test
49-
public fun benchmarkBaselineProfiles() {
50-
benchmark(CompilationMode.Partial(baselineProfileMode = BaselineProfileMode.Require))
44+
@Test fun benchmarkStartupBaselineProfiles() {
45+
benchmarkStartup(CompilationMode.Partial(baselineProfileMode = BaselineProfileMode.Require))
5146
}
5247

53-
@OptIn(ExperimentalTime::class)
54-
public fun benchmark(compilationMode: CompilationMode) {
48+
@Test fun benchmarkStartupFullAOT() {
49+
benchmarkStartup(CompilationMode.Full())
50+
}
51+
52+
@OptIn(ExperimentalTime::class) fun benchmarkStartup(compilationMode: CompilationMode) {
5553
benchmarkRule.measureRepeated(
5654
packageName = PACKAGE_NAME,
57-
metrics = listOf(StartupTimingMetric(), FrameTimingMetric()),
55+
metrics = listOf(StartupTimingMetric()),
5856
iterations = 3,
5957
startupMode = StartupMode.COLD,
6058
compilationMode = compilationMode,
@@ -63,7 +61,7 @@ public class WorkflowBaselineBenchmark {
6361
}
6462
) {
6563
startActivityAndWait()
66-
WorkflowBaselineProfiles.openMazeAndNavigate(device)
64+
DungeonGatherBaselineProfile.openMazeAndNavigate(device)
6765
}
6866
}
6967
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
plugins {
2+
id("com.android.test")
3+
id("org.jetbrains.kotlin.android")
4+
}
5+
6+
// Note: We are not including our defaults from .buildscript as we do not need the base Workflow
7+
// dependencies that those include.
8+
9+
android {
10+
compileSdk = 32
11+
12+
compileOptions {
13+
sourceCompatibility = JavaVersion.VERSION_1_8
14+
targetCompatibility = JavaVersion.VERSION_1_8
15+
}
16+
17+
kotlinOptions {
18+
jvmTarget = "1.8"
19+
}
20+
21+
defaultConfig {
22+
minSdk = 26
23+
targetSdk = 32
24+
25+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
26+
}
27+
28+
buildTypes {
29+
// This benchmark buildType is used for benchmarking, and should function like your
30+
// release build (for example, with minification on). It's signed with a debug key
31+
// for easy local/CI testing.
32+
create("benchmark") {
33+
isDebuggable = true
34+
signingConfig = getByName("debug").signingConfig
35+
matchingFallbacks.add("release")
36+
proguardFile("baseline-proguard-rules.pro")
37+
}
38+
}
39+
40+
// Unclear: there are kotlin_builtins duplication between 1.5.20 and 1.6.10?
41+
packagingOptions {
42+
resources.excludes.add("**/*.kotlin_*")
43+
}
44+
45+
targetProjectPath = ":benchmarks:performance-poetry:complex-poetry"
46+
experimentalProperties["android.experimental.self-instrumenting"] = true
47+
}
48+
49+
dependencies {
50+
implementation(project(":benchmarks:performance-poetry:complex-poetry"))
51+
implementation(project(":samples:containers:poetry"))
52+
implementation(project(":workflow-core"))
53+
54+
implementation(libs.androidx.test.junit)
55+
implementation(libs.androidx.test.espresso.core)
56+
implementation(libs.androidx.test.uiautomator)
57+
implementation(libs.androidx.macro.benchmark)
58+
}
59+
60+
androidComponents {
61+
beforeVariants(selector().all()) {
62+
it.enable = it.buildType == "benchmark"
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools"
4+
package="com.squareup.benchmarks.performance.poetry.complex.benchmark">
5+
6+
<queries>
7+
<package android:name="com.squareup.benchmarks.performance.complex.poetry" />
8+
</queries>
9+
<uses-permission
10+
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
11+
tools:ignore="ScopedStorage" />
12+
</manifest>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-dontobfuscate

0 commit comments

Comments
 (0)