Skip to content

Commit

Permalink
Baseline profile setup for the Catalog app and the published libraries.
Browse files Browse the repository at this point in the history
  • Loading branch information
Bořek Leikep committed Sep 7, 2023
1 parent 5cc7945 commit 7f679fc
Show file tree
Hide file tree
Showing 63 changed files with 698 additions and 48 deletions.
27 changes: 27 additions & 0 deletions .idea/runConfigurations/Generate_Baseline_Profile.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions baselineprofile/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
64 changes: 64 additions & 0 deletions baselineprofile/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
@file:Suppress("UnstableApiUsage")

import com.android.build.api.dsl.ManagedVirtualDevice

plugins {
kotlin("android")
id("com.android.test")
id("androidx.baselineprofile")
id("org.jmailen.kotlinter")
}

android {
namespace = "kiwi.orbit.compose.baselineprofile"
compileSdk = libs.versions.compileSdk.get().toInt()

compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

defaultConfig {
minSdk = 28
targetSdk = libs.versions.targetSdk.get().toInt()

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

lint {
abortOnError = true
warningsAsErrors = true
}

targetProjectPath = ":catalog"

testOptions {
animationsDisabled = true

managedDevices.devices {
create<ManagedVirtualDevice>("pixel6Api34") {
device = "Pixel 6"
apiLevel = 34
systemImageSource = "google"
}
}
}
}

kotlinter {
reporters = arrayOf("json")
}

baselineProfile {
managedDevices += "pixel6Api34"
useConnectedDevices = false
}

dependencies {
implementation(projects.catalog.semantics)

implementation(libs.androidx.benchmark.macro)
implementation(libs.androidx.test.espresso)
implementation(libs.androidx.test.ext.junit)
implementation(libs.androidx.test.uiAutomator)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package com.example.baselineprofile

import androidx.benchmark.macro.MacrobenchmarkScope
import androidx.benchmark.macro.junit4.BaselineProfileRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiScrollable
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
import kiwi.orbit.compose.catalog.semantics.AlertScreenSemantics
import kiwi.orbit.compose.catalog.semantics.ButtonScreenSemantics
import kiwi.orbit.compose.catalog.semantics.DialogScreenSemantics
import kiwi.orbit.compose.catalog.semantics.MainScreenSemantics
import kiwi.orbit.compose.catalog.semantics.PillButtonScreenSemantics
import kiwi.orbit.compose.catalog.semantics.SelectFieldScreenSemantics
import kiwi.orbit.compose.catalog.semantics.SubScreenSemantics
import kiwi.orbit.compose.catalog.semantics.ToastScreenSemantics
import kiwi.orbit.compose.catalog.semantics.TopAppBarScreenSemantics
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

/**
* This test class generates a basic startup baseline profile for the target package.
*
* We recommend you start with this but add important user flows to the profile to improve their performance.
* Refer to the [baseline profile documentation](https://d.android.com/topic/performance/baselineprofiles)
* for more information.
*
* You can run the generator with the Generate Baseline Profile run configuration,
* or directly with `generateBaselineProfile` Gradle task:
* ```
* ./gradlew :catalog:generateReleaseBaselineProfile -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile
* ```
* The run configuration runs the Gradle task and applies filtering to run only the generators.
*
* Check [documentation](https://d.android.com/topic/performance/benchmarking/macrobenchmark-instrumentation-args)
* for more information about available instrumentation arguments.
*
* After you run the generator, you can verify the improvements running the [StartupBenchmarks] benchmark.
**/
@RunWith(AndroidJUnit4::class)
@LargeTest
internal class BaselineProfileGenerator {

private companion object {
const val TIMEOUT = 5000L
}

@get:Rule
val rule = BaselineProfileRule()

@Test
fun generate() {
rule.collect(
packageName = "kiwi.orbit.compose.catalog",
maxIterations = 5,
stableIterations = 2,
) {
pressHome()
startActivityAndWait()

profileSubScreen(MainScreenSemantics.ColorsItemTag)
profileSubScreen(MainScreenSemantics.IconsItemTag)
profileSubScreen(MainScreenSemantics.IllustrationsItemTag)
profileSubScreen(MainScreenSemantics.TypographyItemTag)

profileSubScreen(MainScreenSemantics.AlertItemTag) {
device.findObject(By.res(AlertScreenSemantics.NormalTabTag)).click()
device.findObject(By.res(AlertScreenSemantics.SuppressedTabTag)).click()
device.findObject(By.res(AlertScreenSemantics.InlineTabTag)).click()
}
profileSubScreen(MainScreenSemantics.BadgeItemTag)
profileSubScreen(MainScreenSemantics.BadgeListItemTag)
profileSubScreen(MainScreenSemantics.ButtonItemTag) {
device.findObject(By.res(ButtonScreenSemantics.ButtonTabTag)).click()
device.findObject(By.res(ButtonScreenSemantics.ButtonLinkTabTag)).click()
}
profileSubScreen(MainScreenSemantics.CardItemTag)
profileSubScreen(MainScreenSemantics.CheckboxItemTag)
profileSubScreen(MainScreenSemantics.ChoiceTileItemTag)
profileSubScreen(MainScreenSemantics.CollapseItemTag)
profileSubScreen(MainScreenSemantics.DialogItemTag) {
device.findObject(By.res(DialogScreenSemantics.OrbitDialogButtonTag))
.clickAndWait(Until.newWindow(), TIMEOUT)
device.pressBack()
device.findObject(By.res(DialogScreenSemantics.M3DialogButtonTag))
.clickAndWait(Until.newWindow(), TIMEOUT)
device.pressBack()
device.findObject(By.res(DialogScreenSemantics.M3TimePickerButtonTag))
.clickAndWait(Until.newWindow(), TIMEOUT)
device.pressBack()
device.findObject(By.res(DialogScreenSemantics.M3DatePickerButtonTag))
.clickAndWait(Until.newWindow(), TIMEOUT)
device.pressBack()
}
profileSubScreen(MainScreenSemantics.EmptyStateItemTag)
profileSubScreen(MainScreenSemantics.KeyValueItemTag)
profileSubScreen(MainScreenSemantics.ListItemTag)
profileSubScreen(MainScreenSemantics.ListChoiceItemTag)
profileSubScreen(MainScreenSemantics.LoadingItemTag)
profileSubScreen(MainScreenSemantics.PillButtonItemTag) {
device.findObject(By.res(PillButtonScreenSemantics.ShowWithIconButtonTag)).click()
}
profileSubScreen(MainScreenSemantics.ProgressIndicatorItemTag)
profileSubScreen(MainScreenSemantics.RadioItemTag)
profileSubScreen(MainScreenSemantics.SeatItemTag)
profileSubScreen(MainScreenSemantics.SegmentedSwitchItemTag)
profileSubScreen(MainScreenSemantics.SelectFieldItemTag) {
device.findObject(By.res(SelectFieldScreenSemantics.CountrySelectFieldTag)).click() // open
device.findObject(By.res(SelectFieldScreenSemantics.CountrySelectFieldTag)).click() // close
Thread.sleep(1000L) // back navigation is blocked until the popup is fully closed
}
profileSubScreen(MainScreenSemantics.SliderItemTag)
profileSubScreen(MainScreenSemantics.StepperItemTag)
profileSubScreen(MainScreenSemantics.SurfaceCardItemTag)
profileSubScreen(MainScreenSemantics.SwitchItemTag)
profileSubScreen(MainScreenSemantics.TabsItemTag)
profileSubScreen(MainScreenSemantics.TagItemTag)
profileSubScreen(MainScreenSemantics.TextFieldItemTag)
profileSubScreen(MainScreenSemantics.TileItemTag)
profileSubScreen(MainScreenSemantics.TileGroupItemTag)
profileSubScreen(MainScreenSemantics.TimelineItemTag)
profileSubScreen(MainScreenSemantics.ToastItemTag) {
device.findObject(By.res(ToastScreenSemantics.ToastSignedInButtonTag)).click()
}
profileSubScreen(MainScreenSemantics.TopAppBarItemTag) {
device.findObject(By.res(TopAppBarScreenSemantics.NormalSimpleButtonTag)).click()
device.wait(Until.hasObject(By.res(TopAppBarScreenSemantics.NormalSimpleScreenTag)), TIMEOUT)
device.pressBack()
device.wait(Until.hasObject(By.res(SubScreenSemantics.Tag)), TIMEOUT)
device.findObject(By.res(TopAppBarScreenSemantics.LargeSimpleButtonTag)).click()
device.wait(Until.hasObject(By.res(TopAppBarScreenSemantics.LargeSimpleScreenTag)), TIMEOUT)
device.pressBack()
}
}
}

private fun MacrobenchmarkScope.profileSubScreen(
mainScreenItemTag: String,
profileContent: MacrobenchmarkScope.() -> Unit = {},
) {
val mainScreenScrollable = UiScrollable(UiSelector().scrollable(true))
mainScreenScrollable.scrollIntoView(UiSelector().resourceId(mainScreenItemTag))
device.wait(Until.hasObject(By.res(mainScreenItemTag)), TIMEOUT)

device.findObject(By.res(mainScreenItemTag)).click()
device.wait(Until.hasObject(By.res(SubScreenSemantics.Tag)), TIMEOUT)

profileContent()

device.pressBack()
device.wait(Until.hasObject(By.res(MainScreenSemantics.Tag)), TIMEOUT)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.example.baselineprofile

import androidx.benchmark.macro.BaselineProfileMode
import androidx.benchmark.macro.CompilationMode
import androidx.benchmark.macro.StartupMode
import androidx.benchmark.macro.StartupTimingMetric
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

/**
* This test class benchmarks the speed of app startup.
* Run this benchmark to verify how effective a Baseline Profile is.
* It does this by comparing [CompilationMode.None], which represents the app with no Baseline
* Profiles optimizations, and [CompilationMode.Partial], which uses Baseline Profiles.
*
* Run this benchmark to see startup measurements and captured system traces for verifying
* the effectiveness of your Baseline Profiles. You can run it directly from Android
* Studio as an instrumentation test, or run all benchmarks with this Gradle task:
* ```
* ./gradlew :baselineprofile:connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=Macrobenchmark
* ```
*
* You should run the benchmarks on a physical device, not an Android emulator, because the
* emulator doesn't represent real world performance and shares system resources with its host.
*
* For more information, see the [Macrobenchmark documentation](https://d.android.com/macrobenchmark#create-macrobenchmark)
* and the [instrumentation arguments documentation](https://d.android.com/topic/performance/benchmarking/macrobenchmark-instrumentation-args).
**/
@RunWith(AndroidJUnit4::class)
@LargeTest
internal class StartupBenchmarks {

@get:Rule
val rule = MacrobenchmarkRule()

@Test
fun startupCompilationNone() {
benchmark(CompilationMode.None())
}

@Test
fun startupCompilationBaselineProfiles() {
benchmark(CompilationMode.Partial(BaselineProfileMode.Require))
}

private fun benchmark(compilationMode: CompilationMode) {
rule.measureRepeated(
packageName = "kiwi.orbit.compose.catalog",
metrics = listOf(StartupTimingMetric()),
compilationMode = compilationMode,
startupMode = StartupMode.COLD,
iterations = 10,
setupBlock = { pressHome() },
) {
startActivityAndWait()
}
}

}
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ plugins {
kotlin("multiplatform") version "1.9.10" apply false
kotlin("plugin.serialization") version "1.9.10" apply false
id("com.android.library") version "8.0.2" apply false
id("com.android.test") version "8.0.2" apply false
id("androidx.baselineprofile") version "1.2.0-beta01" apply false
id("app.cash.paparazzi") version "1.3.1" apply false
id("com.google.firebase.appdistribution") version "4.0.0" apply false
id("com.vanniktech.maven.publish.base") version "0.25.3" apply false
Expand Down
6 changes: 5 additions & 1 deletion catalog/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ plugins {
kotlin("android")
kotlin("plugin.serialization")
id("com.android.application")
id("androidx.baselineprofile")
id("org.jmailen.kotlinter")
id("com.google.firebase.appdistribution")
kotlin("plugin.parcelize")
Expand Down Expand Up @@ -127,6 +128,7 @@ dependencies {
implementation(projects.ui)
implementation(projects.icons)
implementation(projects.illustrations)
implementation(projects.catalog.semantics)

implementation(libs.androidx.core)
implementation(libs.androidx.activityCompose)
Expand All @@ -152,12 +154,14 @@ dependencies {
implementation(libs.accompanist.systemController)
implementation(libs.kiwi.navigationComposeTyped)

baselineProfile(projects.baselineprofile)

coreLibraryDesugaring(libs.android.desugarJdk)

debugImplementation(libs.compose.tooling)
debugImplementation(libs.androidx.customView)
debugImplementation(libs.androidx.customViewPoolingContainer)

lintChecks(libs.slack.composeLintChecks)
lintChecks(project(":lint"))
lintChecks(projects.lint)
}
1 change: 1 addition & 0 deletions catalog/semantics/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
28 changes: 28 additions & 0 deletions catalog/semantics/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
plugins {
kotlin("android")
id("com.android.library")
id("org.jmailen.kotlinter")
}

android {
namespace = "kiwi.orbit.compose.catalog.semantics"
compileSdk = libs.versions.compileSdk.get().toInt()

defaultConfig {
minSdk = libs.versions.minSdk.get().toInt()
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

lint {
abortOnError = true
warningsAsErrors = true
}
}

kotlinter {
reporters = arrayOf("json")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package kiwi.orbit.compose.catalog.semantics

object AlertScreenSemantics {
const val NormalTabTag = "alert_screen_normal_tab_tag"
const val SuppressedTabTag = "alert_screen_suppressed_tab_tag"
const val InlineTabTag = "alert_screen_inline_tab_tag"
}
Loading

0 comments on commit 7f679fc

Please sign in to comment.