From c9d90ba642378a62a8cf8e3a7ddd29e1bcc040d9 Mon Sep 17 00:00:00 2001 From: HyunWoo Lee Date: Wed, 30 Aug 2023 00:30:16 +0900 Subject: [PATCH] [feature/#719] Add baselineprofile module [feature/#719] Add baseline-prof when variant is dev [feature/#719] Delete baseline-prof --- app-android/build.gradle.kts | 3 + baselineprofile/.gitignore | 1 + baselineprofile/build.gradle.kts | 64 +++++++++++++++++ baselineprofile/src/main/AndroidManifest.xml | 5 ++ .../confsched2023/BaselineProfileGenerator.kt | 57 +++++++++++++++ .../droidkaigi/confsched2023/Constant.kt | 6 ++ .../confsched2023/StartupBenchmarks.kt | 72 +++++++++++++++++++ build.gradle.kts | 4 +- gradle/libs.versions.toml | 9 +++ settings.gradle.kts | 1 + 10 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 baselineprofile/.gitignore create mode 100644 baselineprofile/build.gradle.kts create mode 100644 baselineprofile/src/main/AndroidManifest.xml create mode 100644 baselineprofile/src/main/java/io/github/droidkaigi/confsched2023/BaselineProfileGenerator.kt create mode 100644 baselineprofile/src/main/java/io/github/droidkaigi/confsched2023/Constant.kt create mode 100644 baselineprofile/src/main/java/io/github/droidkaigi/confsched2023/StartupBenchmarks.kt diff --git a/app-android/build.gradle.kts b/app-android/build.gradle.kts index 8ab192f01..e5166cfdb 100644 --- a/app-android/build.gradle.kts +++ b/app-android/build.gradle.kts @@ -11,6 +11,7 @@ plugins { id("droidkaigi.primitive.android.roborazzi") id("droidkaigi.primitive.kover") id("droidkaigi.primitive.android.osslicenses") + alias(libs.plugins.androidx.baselineprofile) } val keystorePropertiesFile = file("keystore.properties") @@ -118,6 +119,8 @@ dependencies { // Dependency configuration to aggregate Kover coverage reports // TODO: extract report aggregation to build-logic dependencies { + baselineProfile(projects.baselineprofile) + implementation(libs.profileinstaller) kover(projects.appIosShared) kover(projects.feature.about) diff --git a/baselineprofile/.gitignore b/baselineprofile/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/baselineprofile/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/baselineprofile/build.gradle.kts b/baselineprofile/build.gradle.kts new file mode 100644 index 000000000..bf7d136c9 --- /dev/null +++ b/baselineprofile/build.gradle.kts @@ -0,0 +1,64 @@ +import com.android.build.api.dsl.ManagedVirtualDevice + +@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed +plugins { + alias(libs.plugins.androidTest) + alias(libs.plugins.kotlinGradlePlugin) + alias(libs.plugins.androidx.baselineprofile) +} + +android { + namespace = "io.github.droidkaigi.confsched2023" + compileSdk = 34 + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = "11" + } + + defaultConfig { + minSdk = 28 + targetSdk = 34 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = "EMULATOR" + } + + targetProjectPath = ":app-android" + + flavorDimensions += listOf("network") + productFlavors { + create("dev") { dimension = "network" } + create("prod") { dimension = "network" } + } + + testOptions.managedDevices.devices { + create("pixel6Api34") { + device = "Pixel 6" + apiLevel = 34 + systemImageSource = "google" + } + } + + buildFeatures { + buildConfig = true + } +} + +// This is the configuration block for the Baseline Profile plugin. +// You can specify to run the generators on a managed devices or connected devices. +baselineProfile { + managedDevices += "pixel6Api34" + useConnectedDevices = false +} + +dependencies { + implementation(libs.androidxTestExtJunit) + implementation(libs.androidxTestEspressoEspressoCore) + implementation(libs.uiautomator) + implementation(libs.benchmark.macro.junit4) +} diff --git a/baselineprofile/src/main/AndroidManifest.xml b/baselineprofile/src/main/AndroidManifest.xml new file mode 100644 index 000000000..2e977a714 --- /dev/null +++ b/baselineprofile/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/baselineprofile/src/main/java/io/github/droidkaigi/confsched2023/BaselineProfileGenerator.kt b/baselineprofile/src/main/java/io/github/droidkaigi/confsched2023/BaselineProfileGenerator.kt new file mode 100644 index 000000000..2865717a0 --- /dev/null +++ b/baselineprofile/src/main/java/io/github/droidkaigi/confsched2023/BaselineProfileGenerator.kt @@ -0,0 +1,57 @@ +package io.github.droidkaigi.confsched2023 + +import androidx.benchmark.macro.junit4.BaselineProfileRule +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 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 :app-android: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 +class BaselineProfileGenerator { + + @get:Rule + val rule = BaselineProfileRule() + + @Test + fun generate() { + rule.collect(PACKAGE_NAME) { + // This block defines the app's critical user journey. Here we are interested in + // optimizing for app startup. But you can also navigate and scroll + // through your most important UI. + + // Start default activity for your app + pressHome() + startActivityAndWait() + + // TODO Write more interactions to optimize advanced journeys of your app. + // For example: + // 1. Wait until the content is asynchronously loaded + // 2. Scroll the feed content + // 3. Navigate to detail screen + + // Check UiAutomator documentation for more information how to interact with the app. + // https://d.android.com/training/testing/other-components/ui-automator + } + } +} diff --git a/baselineprofile/src/main/java/io/github/droidkaigi/confsched2023/Constant.kt b/baselineprofile/src/main/java/io/github/droidkaigi/confsched2023/Constant.kt new file mode 100644 index 000000000..e74cc6c4d --- /dev/null +++ b/baselineprofile/src/main/java/io/github/droidkaigi/confsched2023/Constant.kt @@ -0,0 +1,6 @@ +package io.github.droidkaigi.confsched2023 + +val PACKAGE_NAME = buildString { + append("io.github.droidkaigi.confsched2023") + append(if (BuildConfig.FLAVOR == "dev") ".dev" else "") +} diff --git a/baselineprofile/src/main/java/io/github/droidkaigi/confsched2023/StartupBenchmarks.kt b/baselineprofile/src/main/java/io/github/droidkaigi/confsched2023/StartupBenchmarks.kt new file mode 100644 index 000000000..19e3d0030 --- /dev/null +++ b/baselineprofile/src/main/java/io/github/droidkaigi/confsched2023/StartupBenchmarks.kt @@ -0,0 +1,72 @@ +package io.github.droidkaigi.confsched2023 + +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 +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 = PACKAGE_NAME, + metrics = listOf(StartupTimingMetric()), + compilationMode = compilationMode, + startupMode = StartupMode.COLD, + iterations = 10, + setupBlock = { + pressHome() + }, + measureBlock = { + startActivityAndWait() + + // TODO Add interactions to wait for when your app is fully drawn. + // The app is fully drawn when Activity.reportFullyDrawn is called. + // For Jetpack Compose, you can use ReportDrawn, ReportDrawnWhen and ReportDrawnAfter + // from the AndroidX Activity library. + + // Check the UiAutomator documentation for more information on how to + // interact with the app. + // https://d.android.com/training/testing/other-components/ui-automator + } + ) + } +} diff --git a/build.gradle.kts b/build.gradle.kts index 8c97fa717..5ffb200db 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,6 +5,8 @@ plugins { alias(libs.plugins.androidGradleLibraryPlugin) apply false alias(libs.plugins.kotlinGradlePlugin) apply false alias(libs.plugins.kotlinxKover) apply false + alias(libs.plugins.androidTest) apply false + alias(libs.plugins.androidx.baselineprofile) apply false } tasks.register("clean", Delete::class) { @@ -19,4 +21,4 @@ buildscript { } } } -} \ No newline at end of file +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b06afacea..5ce8260d7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -38,6 +38,10 @@ detekt = "1.23.1" twitterComposeRule = "0.0.26" lottie = "6.1.0" kover = "0.7.3" +uiautomator = "2.2.0" +benchmark-macro-junit4 = "1.2.0-beta01" +androidx-baselineprofile = "1.2.0-beta01" +profileinstaller = "1.3.1" [libraries] androidGradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } @@ -141,6 +145,9 @@ roborazziCompose = { module = "io.github.takahirom.roborazzi:roborazzi-compose", roborazziRule = { module = "io.github.takahirom.roborazzi:roborazzi-junit-rule", version.ref = "roborazzi" } showkaseRuntime = { group = "com.airbnb.android", name = "showkase", version.ref = "showkase" } showkaseProcessor = { group = "com.airbnb.android", name = "showkase-processor", version.ref = "showkase" } +uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiautomator" } +benchmark-macro-junit4 = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "benchmark-macro-junit4" } +profileinstaller = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "profileinstaller" } [plugins] androidGradlePlugin = { id = "com.android.application", version.ref = "androidGradlePlugin" } @@ -152,6 +159,8 @@ kspGradlePlugin = { id = "com.google.devtools.ksp", version.ref = "ksp" } kotlinxKover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } detektGradlePlugin = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } ossLicensesPlugin = { id = "com.google.android.gms.oss-licenses-plugin", version.ref = "ossLicensesPlugin" } +androidTest = { id = "com.android.test", version.ref = "androidGradlePlugin" } +androidx-baselineprofile = { id = "androidx.baselineprofile", version.ref = "androidx-baselineprofile" } [bundles] plugins = [ diff --git a/settings.gradle.kts b/settings.gradle.kts index cfb3ff2d0..6bce3ea75 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -33,3 +33,4 @@ include( ":core:testing", ":core:common", ) +include(":baselineprofile")