Skip to content

Commit ab66306

Browse files
Add Baseline Profile Module (#928)
* feat: add baseline profile * feat: minor changes * Update generate-baseline-profile.yml * Update generate-baseline-profile.yml * Cleanup and generate baseline profile * feat(build): Add workflow to generate baseline profile * Install GMD image for baseline profile generation * Accept Android licenses * Generate baseline profile for free release variant * Upload generated baseline profiles as artifact * Fix gmd source * feat: Disable 64-bit requirement for Pixel 6 API 34 The 64-bit requirement has been disabled for the "pixel6Api34" managed virtual device. * feat: Configure and run baseline profile generation * Cleared the default managed devices in `baselineprofile/build.gradle.kts` and explicitly added "pixel6Api34". * Configured the GitHub Actions workflow `generate-baseline-profile.yml` to setup the managed device pixel6Api34. * Configured the workflow to build all build type and flavor permutations. * Added the option to show kernel logging and use "swiftshader_indirect" for GPU in the tests. * Refactor: Update baseline profile setup task * Changed the Gradle task from `:benchmarks:pixel6Api34Setup` to `:baselineprofile:pixel6Api34Setup` in the GitHub Actions workflow for setting up the baseline profile generation. * CI: Comment out GMD image installation in baseline profile generation workflow The GMD image installation step in the `generate-baseline-profile.yml` workflow has been commented out. * feat: Add Baseline Profile generation * The Baseline Profile generation is added in the project * Update baselineprofile module to use connected devices for test execution. * Create a new shell script `generateBaselineProfile.sh` to install app and baseline profile and pull file to `baseline-prof.txt` * Modify `generate-baseline-profile.yml` github workflow to generate profile on device using `reactivecircus/android-emulator-runner` action * Fix the package name to `com.yogeshpaliyal.keypass` in `BaselineProfileGenerator.kt` to pull the data. * CI: Update Baseline Profile device to Nexus 6 * Changed the device profile used for Baseline Profile generation from Pixel 6 to Nexus 6 in the GitHub Actions workflow.
1 parent ce90261 commit ab66306

File tree

13 files changed

+286
-19
lines changed

13 files changed

+286
-19
lines changed

.github/workflows/generate-baseline-profile.yml

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,59 @@ jobs:
1111

1212
- name: Setting up project
1313
uses: ./.github/actions/setup
14-
15-
- name: Install GMD image for baseline profile generation
16-
run: yes | "$ANDROID_HOME"/cmdline-tools/latest/bin/sdkmanager "system-images;android-34;aosp_atd;x86_64"
17-
14+
15+
#
16+
# - name: Install GMD image for baseline profile generation
17+
# run: yes | "$ANDROID_HOME"/cmdline-tools/latest/bin/sdkmanager "system-images;android-34;aosp_atd;x86_64"
18+
1819
- name: Accept Android licenses
1920
run: yes | "$ANDROID_HOME"/cmdline-tools/latest/bin/sdkmanager --licenses || true
20-
21-
- name: Generate profile
22-
run: ./gradlew :app:generateProReleaseBaselineProfile
23-
-Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect"
2421

25-
# Upload the entire generated folder and attach it to the CI run
22+
# Now use reactivecircus/android-emulator-runner to spin up an emulator and run our
23+
# baseline profile generator. We need to manually pull the baseline profiles off the
24+
# emulator when using the GA runner
25+
- name: Run benchmark
26+
uses: reactivecircus/android-emulator-runner@v2
27+
with:
28+
api-level: 34
29+
target: google_apis
30+
arch: x86_64
31+
profile: Nexus 6
32+
script: |
33+
./gradlew :app:generateFreeReleaseBaselineProfile
34+
#
35+
# - name: Setup GMD
36+
# run: ./gradlew :baselineprofile:pixel6Api34Setup
37+
# --info
38+
# -Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true
39+
# -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect"
40+
#
41+
# - name: Build all build type and flavor permutations including baseline profiles
42+
# run: ./gradlew :app:assemble
43+
# -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=baselineprofile
44+
# -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect"
45+
# -Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true
46+
#
47+
48+
# - name: Generate profile
49+
# run: ./gradlew :app:generateFreeReleaseBaselineProfile
50+
# -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect"
51+
52+
# Now use reactivecircus/android-emulator-runner to spin up an emulator and run our
53+
# baseline profile generator. We need to manually pull the baseline profiles off the
54+
# emulator when using the GA runner
55+
# - name: Run benchmark
56+
# id: build
57+
# run: |
58+
# # Run our benchmark, enabling only tests using BaselineProfile
59+
# ./gradlew pixel6Api34ProBenchmarkReleaseAndroidTest -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile
60+
# # Need to manually pull the generated profiles from the emulator
61+
# adb pull /sdcard/Android/media/app.tivi.benchmark benchmark/build/outputs/baseline-prof/
62+
63+
# Upload the entire generated folder and attach it to the CI run
2664
- name: Attach baseline profile
2765
uses: actions/upload-artifact@v4
2866
with:
2967
name: Baseline profile output
30-
path: benchmark/build/outputs/baseline-prof
68+
path: app/src/freeRelease/generated/baselineProfiles/
3169

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.gradle
2+
.kotlin
23
local.properties
34
build/
45
!gradle/wrapper/gradle-wrapper.jar
@@ -45,4 +46,4 @@ bin/
4546
.vscode/
4647

4748
### Mac OS ###
48-
.DS_Store
49+
.DS_Store

.java-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
openjdk64-17.0.7
1+
17

app/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ plugins {
77
id("com.google.dagger.hilt.android")
88
id("org.jetbrains.kotlin.plugin.serialization")
99
id("org.jetbrains.kotlin.plugin.compose")
10+
id("androidx.baselineprofile")
1011
}
1112

1213
val appPackageId = "com.yogeshpaliyal.keypass"
@@ -103,13 +104,15 @@ dependencies {
103104

104105
// api(project(":shared"))
105106
api(project(":common"))
107+
implementation("androidx.profileinstaller:profileinstaller:1.3.1")
106108
testImplementation("junit:junit:4.13.2")
107109
androidTestImplementation("androidx.test.ext:junit:1.2.1")
108110
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
109111
androidTestImplementation("androidx.test:core-ktx:1.6.1")
110112
androidTestImplementation("androidx.test.ext:junit-ktx:1.2.1")
111113
// Test rules and transitive dependencies:
112114
androidTestImplementation("androidx.compose.ui:ui-test-junit4:${Versions.compose}")
115+
"baselineProfile"(project(":baselineprofile"))
113116
// Needed for createAndroidComposeRule, but not createComposeRule:
114117
debugImplementation("androidx.compose.ui:ui-test-manifest:${Versions.compose}")
115118
androidTestImplementation("androidx.compose.ui:ui-test-junit4:${Versions.compose}")

baselineprofile/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

baselineprofile/build.gradle.kts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import com.android.build.api.dsl.ManagedVirtualDevice
2+
3+
plugins {
4+
id("com.android.test")
5+
id("org.jetbrains.kotlin.android")
6+
id("androidx.baselineprofile")
7+
}
8+
9+
android {
10+
namespace = "com.yogeshpaliyal.baselineprofile"
11+
compileSdk = 34
12+
13+
compileOptions {
14+
sourceCompatibility = JavaVersion.VERSION_1_8
15+
targetCompatibility = JavaVersion.VERSION_1_8
16+
}
17+
18+
kotlinOptions {
19+
jvmTarget = "1.8"
20+
}
21+
22+
defaultConfig {
23+
minSdk = 28
24+
targetSdk = 34
25+
26+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
27+
}
28+
29+
targetProjectPath = ":app"
30+
31+
flavorDimensions += listOf("default")
32+
productFlavors {
33+
create("free") { dimension = "default" }
34+
create("pro") { dimension = "default" }
35+
}
36+
37+
// This code creates the gradle managed device used to generate baseline profiles.
38+
// To use GMD please invoke generation through the command line:
39+
// ./gradlew :app:generateBaselineProfile
40+
testOptions.managedDevices.devices {
41+
create<ManagedVirtualDevice>("pixel6Api34") {
42+
device = "Pixel 6"
43+
apiLevel = 34
44+
require64Bit = false
45+
systemImageSource = "aosp-atd"
46+
}
47+
}
48+
}
49+
50+
// This is the configuration block for the Baseline Profile plugin.
51+
// You can specify to run the generators on a managed devices or connected devices.
52+
baselineProfile {
53+
// This specifies the managed devices to use that you run the tests on.
54+
// managedDevices.clear()
55+
// managedDevices += "pixel6Api34"
56+
useConnectedDevices = true
57+
}
58+
59+
dependencies {
60+
implementation("androidx.test.ext:junit:1.2.1")
61+
implementation("androidx.test.espresso:espresso-core:3.6.1")
62+
implementation("androidx.test.uiautomator:uiautomator:2.3.0")
63+
implementation("androidx.benchmark:benchmark-macro-junit4:1.2.4")
64+
}
65+
66+
androidComponents {
67+
onVariants { v ->
68+
val artifactsLoader = v.artifacts.getBuiltArtifactsLoader()
69+
v.instrumentationRunnerArguments.put(
70+
"targetAppId",
71+
v.testedApks.map { artifactsLoader.load(it)?.applicationId }
72+
)
73+
}
74+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<manifest />
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.techpaliyal.baselineprofile
2+
3+
import androidx.benchmark.macro.junit4.BaselineProfileRule
4+
import androidx.test.ext.junit.runners.AndroidJUnit4
5+
import androidx.test.filters.LargeTest
6+
import androidx.test.platform.app.InstrumentationRegistry
7+
import org.junit.Rule
8+
import org.junit.Test
9+
import org.junit.runner.RunWith
10+
11+
/**
12+
* This test class generates a basic startup baseline profile for the target package.
13+
*
14+
* We recommend you start with this but add important user flows to the profile to improve their performance.
15+
* Refer to the [baseline profile documentation](https://d.android.com/topic/performance/baselineprofiles)
16+
* for more information.
17+
*
18+
* You can run the generator with the "Generate Baseline Profile" run configuration in Android Studio or
19+
* the equivalent `generateBaselineProfile` gradle task:
20+
* ```
21+
* ./gradlew :app:generateReleaseBaselineProfile
22+
* ```
23+
* The run configuration runs the Gradle task and applies filtering to run only the generators.
24+
*
25+
* Check [documentation](https://d.android.com/topic/performance/benchmarking/macrobenchmark-instrumentation-args)
26+
* for more information about available instrumentation arguments.
27+
*
28+
* After you run the generator, you can verify the improvements running the [StartupBenchmarks] benchmark.
29+
*
30+
* When using this class to generate a baseline profile, only API 33+ or rooted API 28+ are supported.
31+
*
32+
* The minimum required version of androidx.benchmark to generate a baseline profile is 1.2.0.
33+
**/
34+
@RunWith(AndroidJUnit4::class)
35+
@LargeTest
36+
class BaselineProfileGenerator {
37+
38+
@get:Rule
39+
val rule = BaselineProfileRule()
40+
41+
@Test
42+
fun generate() {
43+
// The application id for the running build variant is read from the instrumentation arguments.
44+
rule.collect(
45+
packageName = "com.yogeshpaliyal.keypass",
46+
47+
// See: https://d.android.com/topic/performance/baselineprofiles/dex-layout-optimizations
48+
includeInStartupProfile = true
49+
) {
50+
// This block defines the app's critical user journey. Here we are interested in
51+
// optimizing for app startup. But you can also navigate and scroll through your most important UI.
52+
53+
// Start default activity for your app
54+
pressHome()
55+
startActivityAndWait()
56+
57+
// TODO Write more interactions to optimize advanced journeys of your app.
58+
// For example:
59+
// 1. Wait until the content is asynchronously loaded
60+
// 2. Scroll the feed content
61+
// 3. Navigate to detail screen
62+
63+
// Check UiAutomator documentation for more information how to interact with the app.
64+
// https://d.android.com/training/testing/other-components/ui-automator
65+
}
66+
}
67+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.techpaliyal.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 androidx.test.platform.app.InstrumentationRegistry
11+
import org.junit.Rule
12+
import org.junit.Test
13+
import org.junit.runner.RunWith
14+
15+
/**
16+
* This test class benchmarks the speed of app startup.
17+
* Run this benchmark to verify how effective a Baseline Profile is.
18+
* It does this by comparing [CompilationMode.None], which represents the app with no Baseline
19+
* Profiles optimizations, and [CompilationMode.Partial], which uses Baseline Profiles.
20+
*
21+
* Run this benchmark to see startup measurements and captured system traces for verifying
22+
* the effectiveness of your Baseline Profiles. You can run it directly from Android
23+
* Studio as an instrumentation test, or run all benchmarks for a variant, for example benchmarkRelease,
24+
* with this Gradle task:
25+
* ```
26+
* ./gradlew :baselineprofile:connectedBenchmarkReleaseAndroidTest
27+
* ```
28+
*
29+
* You should run the benchmarks on a physical device, not an Android emulator, because the
30+
* emulator doesn't represent real world performance and shares system resources with its host.
31+
*
32+
* For more information, see the [Macrobenchmark documentation](https://d.android.com/macrobenchmark#create-macrobenchmark)
33+
* and the [instrumentation arguments documentation](https://d.android.com/topic/performance/benchmarking/macrobenchmark-instrumentation-args).
34+
**/
35+
@RunWith(AndroidJUnit4::class)
36+
@LargeTest
37+
class StartupBenchmarks {
38+
39+
@get:Rule
40+
val rule = MacrobenchmarkRule()
41+
42+
@Test
43+
fun startupCompilationNone() =
44+
benchmark(CompilationMode.None())
45+
46+
@Test
47+
fun startupCompilationBaselineProfiles() =
48+
benchmark(CompilationMode.Partial(BaselineProfileMode.Require))
49+
50+
private fun benchmark(compilationMode: CompilationMode) {
51+
// The application id for the running build variant is read from the instrumentation arguments.
52+
rule.measureRepeated(
53+
packageName = InstrumentationRegistry.getArguments().getString("targetAppId")
54+
?: throw Exception("targetAppId not passed as instrumentation runner arg"),
55+
metrics = listOf(StartupTimingMetric()),
56+
compilationMode = compilationMode,
57+
startupMode = StartupMode.COLD,
58+
iterations = 10,
59+
setupBlock = {
60+
pressHome()
61+
},
62+
measureBlock = {
63+
startActivityAndWait()
64+
65+
// TODO Add interactions to wait for when your app is fully drawn.
66+
// The app is fully drawn when Activity.reportFullyDrawn is called.
67+
// For Jetpack Compose, you can use ReportDrawn, ReportDrawnWhen and ReportDrawnAfter
68+
// from the AndroidX Activity library.
69+
70+
// Check the UiAutomator documentation for more information on how to
71+
// interact with the app.
72+
// https://d.android.com/training/testing/other-components/ui-automator
73+
}
74+
)
75+
}
76+
}

build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ plugins {
2828
id("com.google.dagger.hilt.android") version ("2.56.1") apply false
2929
id("com.gradle.enterprise") version("3.19.2") apply false
3030
id("org.jetbrains.kotlin.plugin.serialization") version (Versions.kotlin)
31+
id("com.android.test") version "8.5.1" apply false
32+
id("androidx.baselineprofile") version "1.2.3" apply false
3133
}
3234

3335

gradle.properties

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,10 @@ org.gradle.parallel=true
1717
android.useAndroidX=true
1818
# Kotlin code style for this project: "official" or "obsolete":
1919
kotlin.code.style=official
20-
2120
android.enableR8.fullMode=true
22-
23-
2421
#kotlin.native.enableDependencyPropagation=false
2522
android.defaults.buildfeatures.buildconfig=true
2623
android.nonTransitiveRClass=false
2724
android.nonFinalResIds=false
28-
2925
kotlin.mpp.androidGradlePluginCompatibility.nowarn=true
30-
31-
26+
org.gradle.configuration-cache=true

scripts/generateBaselineProfile.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/bash
2+
3+
./gradlew :app:installFreeBenchmarkRelease
4+
5+
./gradlew :baselineprofile:installFreeBenchmarkRelease
6+
7+
./gradlew :baselineprofile:connectedFreeBenchmarkReleaseAndroidTest -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile
8+
adb pull /sdcard/Android/media/com.yogeshpaliyal.baselineprofile app/src/main/baseline-prof.txt

settings.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ rootProject.name = "KeyPass"
2828
include(":app")
2929
include(":shared")
3030
include(":common")
31-
//include(":desktop")
31+
//include(":desktop")
32+
include(":baselineprofile")

0 commit comments

Comments
 (0)