Skip to content

Commit 9d38c80

Browse files
author
Bořek Leikep
committed
Baseline profile setup for the Catalog app and the published libraries.
1 parent 1b87107 commit 9d38c80

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+697
-48
lines changed

.idea/runConfigurations/Generate_Baseline_Profile.xml

+27
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

baselineprofile/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

baselineprofile/build.gradle.kts

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
@file:Suppress("UnstableApiUsage")
2+
3+
import com.android.build.api.dsl.ManagedVirtualDevice
4+
5+
plugins {
6+
kotlin("android")
7+
id("com.android.test")
8+
id("androidx.baselineprofile")
9+
id("org.jmailen.kotlinter")
10+
}
11+
12+
android {
13+
namespace = "kiwi.orbit.compose.baselineprofile"
14+
compileSdk = libs.versions.compileSdk.get().toInt()
15+
16+
compileOptions {
17+
sourceCompatibility = JavaVersion.VERSION_1_8
18+
targetCompatibility = JavaVersion.VERSION_1_8
19+
}
20+
21+
defaultConfig {
22+
minSdk = 28
23+
targetSdk = libs.versions.targetSdk.get().toInt()
24+
25+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
26+
}
27+
28+
lint {
29+
abortOnError = true
30+
warningsAsErrors = true
31+
}
32+
33+
targetProjectPath = ":catalog"
34+
35+
testOptions {
36+
animationsDisabled = true
37+
38+
managedDevices.devices {
39+
create<ManagedVirtualDevice>("pixel6Api33") {
40+
device = "Pixel 6"
41+
apiLevel = 33
42+
systemImageSource = "google-atd"
43+
}
44+
}
45+
}
46+
}
47+
48+
kotlinter {
49+
reporters = arrayOf("json")
50+
}
51+
52+
baselineProfile {
53+
managedDevices += "pixel6Api33"
54+
useConnectedDevices = false
55+
}
56+
57+
dependencies {
58+
implementation(projects.catalog.semantics)
59+
60+
implementation(libs.androidx.benchmark.macro)
61+
implementation(libs.androidx.test.espresso)
62+
implementation(libs.androidx.test.ext.junit)
63+
implementation(libs.androidx.test.uiAutomator)
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package com.example.baselineprofile
2+
3+
import androidx.benchmark.macro.MacrobenchmarkScope
4+
import androidx.benchmark.macro.junit4.BaselineProfileRule
5+
import androidx.test.ext.junit.runners.AndroidJUnit4
6+
import androidx.test.filters.LargeTest
7+
import androidx.test.uiautomator.By
8+
import androidx.test.uiautomator.UiScrollable
9+
import androidx.test.uiautomator.UiSelector
10+
import androidx.test.uiautomator.Until
11+
import kiwi.orbit.compose.catalog.semantics.AlertScreenSemantics
12+
import kiwi.orbit.compose.catalog.semantics.ButtonScreenSemantics
13+
import kiwi.orbit.compose.catalog.semantics.DialogScreenSemantics
14+
import kiwi.orbit.compose.catalog.semantics.MainScreenSemantics
15+
import kiwi.orbit.compose.catalog.semantics.PillButtonScreenSemantics
16+
import kiwi.orbit.compose.catalog.semantics.SelectFieldScreenSemantics
17+
import kiwi.orbit.compose.catalog.semantics.SubScreenSemantics
18+
import kiwi.orbit.compose.catalog.semantics.ToastScreenSemantics
19+
import kiwi.orbit.compose.catalog.semantics.TopAppBarScreenSemantics
20+
import org.junit.Rule
21+
import org.junit.Test
22+
import org.junit.runner.RunWith
23+
24+
/**
25+
* This test class generates a basic startup baseline profile for the target package.
26+
*
27+
* We recommend you start with this but add important user flows to the profile to improve their performance.
28+
* Refer to the [baseline profile documentation](https://d.android.com/topic/performance/baselineprofiles)
29+
* for more information.
30+
*
31+
* You can run the generator with the Generate Baseline Profile run configuration,
32+
* or directly with `generateBaselineProfile` Gradle task:
33+
* ```
34+
* ./gradlew :catalog:generateReleaseBaselineProfile -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile
35+
* ```
36+
* The run configuration runs the Gradle task and applies filtering to run only the generators.
37+
*
38+
* Check [documentation](https://d.android.com/topic/performance/benchmarking/macrobenchmark-instrumentation-args)
39+
* for more information about available instrumentation arguments.
40+
*
41+
* After you run the generator, you can verify the improvements running the [StartupBenchmarks] benchmark.
42+
**/
43+
@RunWith(AndroidJUnit4::class)
44+
@LargeTest
45+
internal class BaselineProfileGenerator {
46+
47+
private companion object {
48+
const val TIMEOUT = 5000L
49+
}
50+
51+
@get:Rule
52+
val rule = BaselineProfileRule()
53+
54+
@Test
55+
fun generate() {
56+
rule.collect(
57+
packageName = "kiwi.orbit.compose.catalog",
58+
maxIterations = 5,
59+
stableIterations = 2,
60+
) {
61+
pressHome()
62+
startActivityAndWait()
63+
64+
profileSubScreen(MainScreenSemantics.ColorsItemTag)
65+
profileSubScreen(MainScreenSemantics.IconsItemTag)
66+
profileSubScreen(MainScreenSemantics.IllustrationsItemTag)
67+
profileSubScreen(MainScreenSemantics.TypographyItemTag)
68+
69+
profileSubScreen(MainScreenSemantics.AlertItemTag) {
70+
device.findObject(By.res(AlertScreenSemantics.NormalTabTag)).click()
71+
device.findObject(By.res(AlertScreenSemantics.SuppressedTabTag)).click()
72+
device.findObject(By.res(AlertScreenSemantics.InlineTabTag)).click()
73+
}
74+
profileSubScreen(MainScreenSemantics.BadgeItemTag)
75+
profileSubScreen(MainScreenSemantics.BadgeListItemTag)
76+
profileSubScreen(MainScreenSemantics.ButtonItemTag) {
77+
device.findObject(By.res(ButtonScreenSemantics.ButtonTabTag)).click()
78+
device.findObject(By.res(ButtonScreenSemantics.ButtonLinkTabTag)).click()
79+
}
80+
profileSubScreen(MainScreenSemantics.CardItemTag)
81+
profileSubScreen(MainScreenSemantics.CheckboxItemTag)
82+
profileSubScreen(MainScreenSemantics.ChoiceTileItemTag)
83+
profileSubScreen(MainScreenSemantics.CollapseItemTag)
84+
profileSubScreen(MainScreenSemantics.DialogItemTag) {
85+
device.findObject(By.res(DialogScreenSemantics.OrbitDialogButtonTag))
86+
.clickAndWait(Until.newWindow(), TIMEOUT)
87+
device.pressBack()
88+
device.findObject(By.res(DialogScreenSemantics.M3DialogButtonTag))
89+
.clickAndWait(Until.newWindow(), TIMEOUT)
90+
device.pressBack()
91+
device.findObject(By.res(DialogScreenSemantics.M3TimePickerButtonTag))
92+
.clickAndWait(Until.newWindow(), TIMEOUT)
93+
device.pressBack()
94+
device.findObject(By.res(DialogScreenSemantics.M3DatePickerButtonTag))
95+
.clickAndWait(Until.newWindow(), TIMEOUT)
96+
device.pressBack()
97+
}
98+
profileSubScreen(MainScreenSemantics.EmptyStateItemTag)
99+
profileSubScreen(MainScreenSemantics.KeyValueItemTag)
100+
profileSubScreen(MainScreenSemantics.ListItemTag)
101+
profileSubScreen(MainScreenSemantics.ListChoiceItemTag)
102+
profileSubScreen(MainScreenSemantics.LoadingItemTag)
103+
profileSubScreen(MainScreenSemantics.PillButtonItemTag) {
104+
device.findObject(By.res(PillButtonScreenSemantics.ShowWithIconButtonTag)).click()
105+
}
106+
profileSubScreen(MainScreenSemantics.ProgressIndicatorItemTag)
107+
profileSubScreen(MainScreenSemantics.RadioItemTag)
108+
profileSubScreen(MainScreenSemantics.SeatItemTag)
109+
profileSubScreen(MainScreenSemantics.SegmentedSwitchItemTag)
110+
profileSubScreen(MainScreenSemantics.SelectFieldItemTag) {
111+
device.findObject(By.res(SelectFieldScreenSemantics.CountrySelectFieldTag)).click() // open
112+
device.findObject(By.res(SelectFieldScreenSemantics.CountrySelectFieldTag)).click() // close
113+
Thread.sleep(1000L) // back navigation is blocked until the popup is fully closed
114+
}
115+
profileSubScreen(MainScreenSemantics.SliderItemTag)
116+
profileSubScreen(MainScreenSemantics.StepperItemTag)
117+
profileSubScreen(MainScreenSemantics.SurfaceCardItemTag)
118+
profileSubScreen(MainScreenSemantics.SwitchItemTag)
119+
profileSubScreen(MainScreenSemantics.TabsItemTag)
120+
profileSubScreen(MainScreenSemantics.TagItemTag)
121+
profileSubScreen(MainScreenSemantics.TextFieldItemTag)
122+
profileSubScreen(MainScreenSemantics.TileItemTag)
123+
profileSubScreen(MainScreenSemantics.TileGroupItemTag)
124+
profileSubScreen(MainScreenSemantics.TimelineItemTag)
125+
profileSubScreen(MainScreenSemantics.ToastItemTag) {
126+
device.findObject(By.res(ToastScreenSemantics.ToastSignedInButtonTag)).click()
127+
}
128+
profileSubScreen(MainScreenSemantics.TopAppBarItemTag) {
129+
device.findObject(By.res(TopAppBarScreenSemantics.NormalSimpleButtonTag)).click()
130+
device.wait(Until.hasObject(By.res(TopAppBarScreenSemantics.NormalSimpleScreenTag)), TIMEOUT)
131+
device.pressBack()
132+
device.wait(Until.hasObject(By.res(SubScreenSemantics.Tag)), TIMEOUT)
133+
device.findObject(By.res(TopAppBarScreenSemantics.LargeSimpleButtonTag)).click()
134+
device.wait(Until.hasObject(By.res(TopAppBarScreenSemantics.LargeSimpleScreenTag)), TIMEOUT)
135+
device.pressBack()
136+
}
137+
}
138+
}
139+
140+
private fun MacrobenchmarkScope.profileSubScreen(
141+
mainScreenItemTag: String,
142+
profileContent: MacrobenchmarkScope.() -> Unit = {},
143+
) {
144+
val mainScreenScrollable = UiScrollable(UiSelector().scrollable(true))
145+
mainScreenScrollable.scrollIntoView(UiSelector().resourceId(mainScreenItemTag))
146+
device.wait(Until.hasObject(By.res(mainScreenItemTag)), TIMEOUT)
147+
148+
device.findObject(By.res(mainScreenItemTag)).click()
149+
device.wait(Until.hasObject(By.res(SubScreenSemantics.Tag)), TIMEOUT)
150+
151+
profileContent()
152+
153+
device.pressBack()
154+
device.wait(Until.hasObject(By.res(MainScreenSemantics.Tag)), TIMEOUT)
155+
}
156+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.example.baselineprofile
2+
3+
import androidx.benchmark.macro.BaselineProfileMode
4+
import androidx.benchmark.macro.CompilationMode
5+
import androidx.benchmark.macro.StartupMode
6+
import androidx.benchmark.macro.StartupTimingMetric
7+
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
8+
import androidx.test.ext.junit.runners.AndroidJUnit4
9+
import androidx.test.filters.LargeTest
10+
import org.junit.Rule
11+
import org.junit.Test
12+
import org.junit.runner.RunWith
13+
14+
/**
15+
* This test class benchmarks the speed of app startup.
16+
* Run this benchmark to verify how effective a Baseline Profile is.
17+
* It does this by comparing [CompilationMode.None], which represents the app with no Baseline
18+
* Profiles optimizations, and [CompilationMode.Partial], which uses Baseline Profiles.
19+
*
20+
* Run this benchmark to see startup measurements and captured system traces for verifying
21+
* the effectiveness of your Baseline Profiles. You can run it directly from Android
22+
* Studio as an instrumentation test, or run all benchmarks with this Gradle task:
23+
* ```
24+
* ./gradlew :baselineprofile:connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=Macrobenchmark
25+
* ```
26+
*
27+
* You should run the benchmarks on a physical device, not an Android emulator, because the
28+
* emulator doesn't represent real world performance and shares system resources with its host.
29+
*
30+
* For more information, see the [Macrobenchmark documentation](https://d.android.com/macrobenchmark#create-macrobenchmark)
31+
* and the [instrumentation arguments documentation](https://d.android.com/topic/performance/benchmarking/macrobenchmark-instrumentation-args).
32+
**/
33+
@RunWith(AndroidJUnit4::class)
34+
@LargeTest
35+
internal class StartupBenchmarks {
36+
37+
@get:Rule
38+
val rule = MacrobenchmarkRule()
39+
40+
@Test
41+
fun startupCompilationNone() {
42+
benchmark(CompilationMode.None())
43+
}
44+
45+
@Test
46+
fun startupCompilationBaselineProfiles() {
47+
benchmark(CompilationMode.Partial(BaselineProfileMode.Require))
48+
}
49+
50+
private fun benchmark(compilationMode: CompilationMode) {
51+
rule.measureRepeated(
52+
packageName = "kiwi.orbit.compose.catalog",
53+
metrics = listOf(StartupTimingMetric()),
54+
compilationMode = compilationMode,
55+
startupMode = StartupMode.COLD,
56+
iterations = 10,
57+
setupBlock = { pressHome() },
58+
) {
59+
startActivityAndWait()
60+
}
61+
}
62+
}

build.gradle.kts

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ plugins {
1414
kotlin("multiplatform") version "1.9.10" apply false
1515
kotlin("plugin.serialization") version "1.9.10" apply false
1616
id("com.android.library") version "8.0.2" apply false
17+
id("com.android.test") version "8.0.2" apply false
18+
id("androidx.baselineprofile") version "1.2.0-beta01" apply false
1719
id("app.cash.paparazzi") version "1.3.1" apply false
1820
id("com.google.firebase.appdistribution") version "4.0.0" apply false
1921
id("com.vanniktech.maven.publish.base") version "0.25.3" apply false

catalog/build.gradle.kts

+5-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ plugins {
77
kotlin("android")
88
kotlin("plugin.serialization")
99
id("com.android.application")
10+
id("androidx.baselineprofile")
1011
id("org.jmailen.kotlinter")
1112
id("com.google.firebase.appdistribution")
1213
kotlin("plugin.parcelize")
@@ -127,6 +128,7 @@ dependencies {
127128
implementation(projects.ui)
128129
implementation(projects.icons)
129130
implementation(projects.illustrations)
131+
implementation(projects.catalog.semantics)
130132

131133
implementation(libs.androidx.core)
132134
implementation(libs.androidx.activityCompose)
@@ -152,12 +154,14 @@ dependencies {
152154
implementation(libs.accompanist.systemController)
153155
implementation(libs.kiwi.navigationComposeTyped)
154156

157+
baselineProfile(projects.baselineprofile)
158+
155159
coreLibraryDesugaring(libs.android.desugarJdk)
156160

157161
debugImplementation(libs.compose.tooling)
158162
debugImplementation(libs.androidx.customView)
159163
debugImplementation(libs.androidx.customViewPoolingContainer)
160164

161165
lintChecks(libs.slack.composeLintChecks)
162-
lintChecks(project(":lint"))
166+
lintChecks(projects.lint)
163167
}

catalog/semantics/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

catalog/semantics/build.gradle.kts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
plugins {
2+
kotlin("android")
3+
id("com.android.library")
4+
id("org.jmailen.kotlinter")
5+
}
6+
7+
android {
8+
namespace = "kiwi.orbit.compose.catalog.semantics"
9+
compileSdk = libs.versions.compileSdk.get().toInt()
10+
11+
defaultConfig {
12+
minSdk = libs.versions.minSdk.get().toInt()
13+
}
14+
15+
compileOptions {
16+
sourceCompatibility = JavaVersion.VERSION_1_8
17+
targetCompatibility = JavaVersion.VERSION_1_8
18+
}
19+
20+
lint {
21+
abortOnError = true
22+
warningsAsErrors = true
23+
}
24+
}
25+
26+
kotlinter {
27+
reporters = arrayOf("json")
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package kiwi.orbit.compose.catalog.semantics
2+
3+
object AlertScreenSemantics {
4+
const val NormalTabTag = "alert_screen_normal_tab_tag"
5+
const val SuppressedTabTag = "alert_screen_suppressed_tab_tag"
6+
const val InlineTabTag = "alert_screen_inline_tab_tag"
7+
}

0 commit comments

Comments
 (0)