Skip to content

Commit 819d814

Browse files
author
Bořek Leikep
committed
Baseline profile setup for the Catalog app and the published libraries.
1 parent c78bcc9 commit 819d814

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

+688
-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

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

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.1.1" apply false
17+
id("com.android.test") version "8.1.1" 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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package kiwi.orbit.compose.catalog.semantics
2+
3+
object ButtonScreenSemantics {
4+
const val ButtonTabTag = "button_screen_button_tab_tag"
5+
const val ButtonLinkTabTag = "button_screen_button_link_tab_tag"
6+
}

0 commit comments

Comments
 (0)