Skip to content

Commit aa8ce0e

Browse files
Add automated baseline profile generation (#880)
Baseline profile generation is disabled for the PR level Build task. Release tasks require a fresh baseline profile. A new profile is generated using the baseline profile Gradle plugin. * Prepare for usage of dex layout optimizations which can be actively used once NiA switches to AGP 8.2+. * Add GMD config to release build * Switch to macos-latest * Update names for StartupBenchmark tests to better reflect states * Stable release and recent GMD device * Reduce flakiness by adding wait to benchmark * More convenient waiting for objects * Rename junit dependency to androidx-junit * Only run baseline profile benchmarks during GH workflow * Enable automatic BP generation for only release builds * Disable BP generation from Build workflow * Specify modules and skip benchmarking Build workflow Bug: b/299334172
1 parent d4ef172 commit aa8ce0e

File tree

18 files changed

+244
-62
lines changed

18 files changed

+244
-62
lines changed

.github/workflows/Build.yaml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,19 @@ jobs:
7373
- name: Run local tests
7474
if: always()
7575
run: ./gradlew testDemoDebug testProdDebug :lint:test
76-
76+
# Replace task exclusions with `-Pandroidx.baselineprofile.skipgeneration` when
77+
# https://android-review.googlesource.com/c/platform/frameworks/support/+/2602790 landed in a
78+
# release build
7779
- name: Build all build type and flavor permutations
78-
run: ./gradlew assemble
80+
run: ./gradlew :app:assemble :benchmarks:assemble
81+
-x pixel6Api33ProdNonMinifiedReleaseAndroidTest
82+
-x pixel6Api33ProdNonMinifiedBenchmarkAndroidTest
83+
-x pixel6Api33DemoNonMinifiedReleaseAndroidTest
84+
-x pixel6Api33DemoNonMinifiedBenchmarkAndroidTest
85+
-x collectDemoNonMinifiedReleaseBaselineProfile
86+
-x collectDemoNonMinifiedBenchmarkBaselineProfile
87+
-x collectProdNonMinifiedReleaseBaselineProfile
88+
-x collectProdNonMinifiedBenchmarkBaselineProfile
7989

8090
- name: Upload build outputs (APKs)
8191
uses: actions/upload-artifact@v3

.github/workflows/Release.yml

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ on:
77

88
jobs:
99
build:
10-
runs-on: ubuntu-latest
11-
timeout-minutes: 45
10+
runs-on: macos-latest
11+
timeout-minutes: 120
1212

1313
steps:
1414
- name: Checkout
@@ -26,9 +26,19 @@ jobs:
2626
distribution: 'zulu'
2727
java-version: 17
2828

29-
- name: Build app
30-
run: ./gradlew :app:assembleDemoRelease
29+
- name: Install GMD image for baseline profile generation
30+
run: yes | "$ANDROID_HOME"/cmdline-tools/latest/bin/sdkmanager "system-images;android-33;aosp_atd;x86_64"
31+
32+
- name: Accept Android licenses
33+
run: yes | "$ANDROID_HOME"/cmdline-tools/latest/bin/sdkmanager --licenses || true
3134

35+
- name: Build release variant including baseline profile generation
36+
run: ./gradlew :app:assembleDemoRelease
37+
-Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile
38+
-Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect"
39+
-Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true
40+
-Pandroid.experimental.androidTest.numManagedDeviceShards=1
41+
-Pandroid.experimental.testOptions.managedDevices.maxConcurrentDevices=1
3242
- name: Create Release
3343
id: create_release
3444
uses: actions/create-release@v1

app/build.gradle.kts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ plugins {
2424
id("jacoco")
2525
alias(libs.plugins.nowinandroid.android.application.firebase)
2626
id("com.google.android.gms.oss-licenses-plugin")
27+
alias(libs.plugins.baselineprofile)
2728
}
2829

2930
android {
@@ -43,7 +44,7 @@ android {
4344
debug {
4445
applicationIdSuffix = NiaBuildType.DEBUG.applicationIdSuffix
4546
}
46-
val release by getting {
47+
val release = getByName("release") {
4748
isMinifyEnabled = true
4849
applicationIdSuffix = NiaBuildType.RELEASE.applicationIdSuffix
4950
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
@@ -52,6 +53,8 @@ android {
5253
// who clones the code to sign and run the release variant, use the debug signing key.
5354
// TODO: Abstract the signing configuration to a separate file to avoid hardcoding this.
5455
signingConfig = signingConfigs.getByName("debug")
56+
// Ensure Baseline Profile is fresh for release builds.
57+
baselineProfile.automaticGenerationDuringBuild = true
5558
}
5659
create("benchmark") {
5760
// Enable all the optimizations from release build through initWith(release).
@@ -121,6 +124,8 @@ dependencies {
121124
implementation(libs.kotlinx.coroutines.guava)
122125
implementation(libs.coil.kt)
123126

127+
baselineProfile(project(":benchmarks"))
128+
124129
// Core functions
125130
testImplementation(projects.core.testing)
126131
testImplementation(projects.core.datastoreTest)
@@ -133,3 +138,9 @@ dependencies {
133138
kspTest(libs.hilt.compiler)
134139

135140
}
141+
142+
baselineProfile {
143+
// Don't build on every iteration of a full assemble.
144+
// Instead enable generation directly for the release build variant.
145+
automaticGenerationDuringBuild = false
146+
}

benchmarks/build.gradle.kts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import com.google.samples.apps.nowinandroid.NiaBuildType
1717
import com.google.samples.apps.nowinandroid.configureFlavors
1818

1919
plugins {
20+
alias(libs.plugins.baselineprofile)
2021
alias(libs.plugins.nowinandroid.android.test)
2122
}
2223

@@ -62,10 +63,27 @@ android {
6263
)
6364
}
6465

66+
testOptions.managedDevices.devices {
67+
create<com.android.build.api.dsl.ManagedVirtualDevice>("pixel6Api33") {
68+
device = "Pixel 6"
69+
apiLevel = 33
70+
systemImageSource = "aosp"
71+
}
72+
}
73+
6574
targetProjectPath = ":app"
6675
experimentalProperties["android.experimental.self-instrumenting"] = true
6776
}
6877

78+
baselineProfile {
79+
// This specifies the managed devices to use that you run the tests on.
80+
managedDevices += "pixel6Api33"
81+
82+
// Don't use a connected device but rely on a GMD for consistency between local and CI builds.
83+
useConnectedDevices = false
84+
85+
}
86+
6987
dependencies {
7088
implementation(libs.androidx.benchmark.macro)
7189
implementation(libs.androidx.test.core)
@@ -75,9 +93,3 @@ dependencies {
7593
implementation(libs.androidx.test.runner)
7694
implementation(libs.androidx.test.uiautomator)
7795
}
78-
79-
androidComponents {
80-
beforeVariants {
81-
it.enable = it.buildType == "benchmark"
82-
}
83-
}

benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/GeneralActions.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ import android.Manifest.permission
2020
import android.os.Build.VERSION.SDK_INT
2121
import android.os.Build.VERSION_CODES.TIRAMISU
2222
import androidx.benchmark.macro.MacrobenchmarkScope
23+
import androidx.test.uiautomator.By
24+
import androidx.test.uiautomator.BySelector
25+
import androidx.test.uiautomator.UiObject2
26+
import androidx.test.uiautomator.Until
2327

2428
/**
2529
* Because the app under test is different from the one running the instrumentation test,
@@ -42,3 +46,27 @@ fun MacrobenchmarkScope.allowNotifications() {
4246
device.executeShellCommand(command)
4347
}
4448
}
49+
50+
/**
51+
* Wraps starting the default activity, waiting for it to start and then allowing notifications in
52+
* one convenient call.
53+
*/
54+
fun MacrobenchmarkScope.startActivityAndAllowNotifications() {
55+
startActivityAndWait()
56+
allowNotifications()
57+
}
58+
59+
/**
60+
* Waits for and returns the `niaTopAppBar`
61+
*/
62+
fun MacrobenchmarkScope.getTopAppBar(): UiObject2 {
63+
device.wait(Until.hasObject(By.res("niaTopAppBar")), 2_000)
64+
return device.findObject(By.res("niaTopAppBar"))
65+
}
66+
67+
/**
68+
* Waits for an object on the top app bar, passed in as [selector].
69+
*/
70+
fun MacrobenchmarkScope.waitForObjectOnTopAppBar(selector: BySelector, timeout: Long = 2_000) {
71+
getTopAppBar().wait(Until.hasObject(selector), timeout)
72+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2022 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.samples.apps.nowinandroid.baselineprofile
18+
19+
import androidx.benchmark.macro.junit4.BaselineProfileRule
20+
import com.google.samples.apps.nowinandroid.PACKAGE_NAME
21+
import com.google.samples.apps.nowinandroid.bookmarks.goToBookmarksScreen
22+
import com.google.samples.apps.nowinandroid.startActivityAndAllowNotifications
23+
import org.junit.Rule
24+
import org.junit.Test
25+
26+
/**
27+
* Baseline Profile of the "Bookmarks" screen
28+
*/
29+
class BookmarksBaselineProfile {
30+
@get:Rule val baselineProfileRule = BaselineProfileRule()
31+
32+
@Test
33+
fun generate() =
34+
baselineProfileRule.collect(PACKAGE_NAME) {
35+
startActivityAndAllowNotifications()
36+
37+
// Navigate to saved screen
38+
goToBookmarksScreen()
39+
}
40+
}

benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/BaselineProfileGenerator.kt renamed to benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/ForYouBaselineProfile.kt

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,43 +18,27 @@ package com.google.samples.apps.nowinandroid.baselineprofile
1818

1919
import androidx.benchmark.macro.junit4.BaselineProfileRule
2020
import com.google.samples.apps.nowinandroid.PACKAGE_NAME
21-
import com.google.samples.apps.nowinandroid.allowNotifications
22-
import com.google.samples.apps.nowinandroid.bookmarks.goToBookmarksScreen
2321
import com.google.samples.apps.nowinandroid.foryou.forYouScrollFeedDownUp
2422
import com.google.samples.apps.nowinandroid.foryou.forYouSelectTopics
2523
import com.google.samples.apps.nowinandroid.foryou.forYouWaitForContent
26-
import com.google.samples.apps.nowinandroid.interests.goToInterestsScreen
27-
import com.google.samples.apps.nowinandroid.interests.interestsScrollTopicsDownUp
24+
import com.google.samples.apps.nowinandroid.startActivityAndAllowNotifications
2825
import org.junit.Rule
2926
import org.junit.Test
3027

3128
/**
32-
* Generates a baseline profile which can be copied to `app/src/main/baseline-prof.txt`.
29+
* Baseline Profile of the "For You" screen
3330
*/
34-
class BaselineProfileGenerator {
31+
class ForYouBaselineProfile {
3532
@get:Rule val baselineProfileRule = BaselineProfileRule()
3633

3734
@Test
3835
fun generate() =
3936
baselineProfileRule.collect(PACKAGE_NAME) {
40-
// This block defines the app's critical user journey. Here we are interested in
41-
// optimizing for app startup. But you can also navigate and scroll
42-
// through your most important UI.
43-
allowNotifications()
44-
pressHome()
45-
startActivityAndWait()
46-
allowNotifications()
37+
startActivityAndAllowNotifications()
4738

4839
// Scroll the feed critical user journey
4940
forYouWaitForContent()
5041
forYouSelectTopics(true)
5142
forYouScrollFeedDownUp()
52-
53-
// Navigate to saved screen
54-
goToBookmarksScreen()
55-
56-
// Navigate to interests screen
57-
goToInterestsScreen()
58-
interestsScrollTopicsDownUp()
5943
}
6044
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2022 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.samples.apps.nowinandroid.baselineprofile
18+
19+
import androidx.benchmark.macro.junit4.BaselineProfileRule
20+
import com.google.samples.apps.nowinandroid.PACKAGE_NAME
21+
import com.google.samples.apps.nowinandroid.interests.goToInterestsScreen
22+
import com.google.samples.apps.nowinandroid.interests.interestsScrollTopicsDownUp
23+
import com.google.samples.apps.nowinandroid.startActivityAndAllowNotifications
24+
import org.junit.Rule
25+
import org.junit.Test
26+
27+
/**
28+
* Baseline Profile of the "Interests" screen
29+
*/
30+
class InterestsBaselineProfile {
31+
@get:Rule val baselineProfileRule = BaselineProfileRule()
32+
33+
@Test
34+
fun generate() =
35+
baselineProfileRule.collect(PACKAGE_NAME) {
36+
startActivityAndAllowNotifications()
37+
38+
// Navigate to interests screen
39+
goToInterestsScreen()
40+
interestsScrollTopicsDownUp()
41+
}
42+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2022 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.samples.apps.nowinandroid.baselineprofile
18+
19+
import androidx.benchmark.macro.junit4.BaselineProfileRule
20+
import com.google.samples.apps.nowinandroid.PACKAGE_NAME
21+
import com.google.samples.apps.nowinandroid.startActivityAndAllowNotifications
22+
import org.junit.Rule
23+
import org.junit.Test
24+
25+
/**
26+
* Baseline Profile for app startup. This profile also enables using [Dex Layout Optimizations](https://developer.android.com/topic/performance/baselineprofiles/dex-layout-optimizations)
27+
* via the `includeInStartupProfile` parameter.
28+
*/
29+
class StartupBaselineProfile {
30+
@get:Rule val baselineProfileRule = BaselineProfileRule()
31+
32+
@Test
33+
fun generate() =
34+
baselineProfileRule.collect(
35+
PACKAGE_NAME,
36+
includeInStartupProfile = true,
37+
) {
38+
startActivityAndAllowNotifications()
39+
}
40+
}

benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/bookmarks/BookmarksActions.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ package com.google.samples.apps.nowinandroid.bookmarks
1818

1919
import androidx.benchmark.macro.MacrobenchmarkScope
2020
import androidx.test.uiautomator.By
21-
import androidx.test.uiautomator.Until
21+
import com.google.samples.apps.nowinandroid.waitForObjectOnTopAppBar
2222

2323
fun MacrobenchmarkScope.goToBookmarksScreen() {
24-
device.findObject(By.text("Saved")).click()
24+
val savedSelector = By.text("Saved")
25+
val savedButton = device.findObject(savedSelector)
26+
savedButton.click()
2527
device.waitForIdle()
2628
// Wait until saved title are shown on screen
27-
device.wait(Until.hasObject(By.res("niaTopAppBar")), 2_000)
28-
val topAppBar = device.findObject(By.res("niaTopAppBar"))
29-
topAppBar.wait(Until.hasObject(By.text("Saved")), 2_000)
29+
waitForObjectOnTopAppBar(savedSelector)
3030
}

0 commit comments

Comments
 (0)