Add automated baseline profile generation (#1349)

* Add automated baseline profile generation

* Create benchmark build type

And remove the `enabledRules` argument as it will be injected by
baseline profile gradle plugin.
This commit is contained in:
FooIbar authored and GitHub committed 2024-06-27 11:46:02 +08:00
1 parent 4dd6d68163
commit cb2d99bdc1
13 files changed
+292 -12

No files matched your search

+82
View File
@@ -0,0 +1,82 @@
name: Baseline profile generation
on:
schedule:
- cron: '30 0 * * 0'
workflow_dispatch:
push:
paths:
- 'benchmark/**'
- '.github/workflows/baseline-profile.yml'
jobs:
baseline-profile:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
- name: Free disk space
run: rm -rf ${ANDROID_SDK_ROOT}/ndk
- name: Install NDK
run: echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "ndk;27.0.11902837"
- name: Install Rust Toolchain
run: rustup update && rustup target add armv7-linux-androideabi aarch64-linux-android x86_64-linux-android && rustup component add rustfmt clippy
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: "./app/src/main/rust"
shared-key: ${{ runner.os }}
save-if: "false"
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
cache-encryption-key: ${{ secrets.GradleEncryptionKey }}
cache-read-only: true
- name: CMake Cache
uses: actions/cache/restore@v4
with:
path: app/.cxx
key: cmake-
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Build
run: ./gradlew generateBaselineProfile
-Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect"
- name: Update baseline profile
if: github.ref == 'refs/heads/main'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config push.autoSetupRemote true
git switch -c chore/baseline-profile
if git commit -am "Update baseline profile"; then
git push
gh pr create -f
fi
- name: Upload reports
if: always()
uses: actions/upload-artifact@v4
with:
name: reports
path: benchmark/build/reports
+15
View File
@@ -14,6 +14,7 @@ plugins {
alias(libs.plugins.spotless)
alias(libs.plugins.aboutlibrariesPlugin)
alias(libs.plugins.composeCompilerReportGenerator)
alias(libs.plugins.baselineprofile)
}
val supportedAbis = arrayOf("arm64-v8a", "x86_64", "armeabi-v7a")
@@ -151,6 +152,13 @@ android {
abortOnError = false
}
}
create("benchmark") {
initWith(buildTypes.getByName("release"))
matchingFallbacks += listOf("release")
applicationIdSuffix = ".benchmark"
signingConfig = signingConfigs.getByName("debug")
isDebuggable = false
}
}
buildFeatures {
@@ -178,6 +186,10 @@ androidComponents {
}
}
baselineProfile {
mergeIntoMain = true
}
dependencies {
// https://developer.android.com/jetpack/androidx/releases/activity
implementation(libs.androidx.activity.compose)
@@ -255,6 +267,9 @@ dependencies {
implementation(libs.cronet.embedded)
implementation(libs.androidx.profileinstaller)
"baselineProfile"(project(":benchmark"))
debugImplementation(libs.compose.ui.tooling)
implementation(libs.compose.ui.tooling.preview)
}
@@ -135,6 +135,7 @@ import com.hippo.ehviewer.ui.tools.LabeledCheckbox
import com.hippo.ehviewer.ui.tools.LocalDialogState
import com.hippo.ehviewer.ui.tools.LocalWindowSizeClass
import com.hippo.ehviewer.updater.AppUpdater
import com.hippo.ehviewer.util.AppConfig
import com.hippo.ehviewer.util.addTextToClipboard
import com.hippo.ehviewer.util.calculateFraction
import com.hippo.ehviewer.util.displayString
@@ -242,17 +243,19 @@ class MainActivity : EhActivity() {
}
}
LaunchedEffect(Unit) {
runCatching { dialogState.checkDownloadLocation() }
runCatching { dialogState.checkAppLinkVerify() }
runSuspendCatching {
withIOContext {
AppUpdater.checkForUpdate()?.let {
dialogState.showNewVersion(this@MainActivity, it)
if (!AppConfig.isBenchmark) {
LaunchedEffect(Unit) {
runCatching { dialogState.checkDownloadLocation() }
runCatching { dialogState.checkAppLinkVerify() }
runSuspendCatching {
withIOContext {
AppUpdater.checkForUpdate()?.let {
dialogState.showNewVersion(this@MainActivity, it)
}
}
}.onFailure {
snackbarState.showSnackbar(getString(R.string.update_failed, it.displayString()))
}
}.onFailure {
snackbarState.showSnackbar(getString(R.string.update_failed, it.displayString()))
}
}
@@ -3,6 +3,7 @@ package com.hippo.ehviewer.ui.screen
import android.content.Context
import android.net.Uri
import android.view.ViewConfiguration
import androidx.activity.compose.ReportDrawnWhen
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
@@ -78,6 +79,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.lerp
import androidx.lifecycle.viewModelScope
import androidx.paging.LoadState
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingSource
@@ -252,6 +254,7 @@ fun GalleryListScreen(lub: ListUrlBuilder, navigator: DestinationsNavigator) = c
}
}.flow.cachedIn(viewModelScope)
}.collectAsLazyPagingItems()
ReportDrawnWhen { data.loadState.refresh !is LoadState.Loading }
FavouriteStatusRouter.Observe(data)
val listMode by Settings.listMode.collectAsState()
@@ -37,6 +37,8 @@ object AppConfig {
it in setOf("arm64-v8a", "x86_64", "armeabi-v7a")
} ?: "universal"
val isBenchmark = "nonMinified" in BuildConfig.BUILD_TYPE || "benchmark" in BuildConfig.BUILD_TYPE
fun matchVariant(name: String) = name.contains(BuildConfig.FLAVOR) && name.contains(abi)
val commitTime = ParserUtils.formatDate(BuildConfig.COMMIT_TIME * 1000)
+1
View File
@@ -0,0 +1 @@
/build
+65
View File
@@ -0,0 +1,65 @@
import com.android.build.api.dsl.ManagedVirtualDevice
plugins {
alias(libs.plugins.android.test)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.baselineprofile)
}
android {
namespace = "com.ehviewer.baselineprofile"
compileSdk = 34
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
defaultConfig {
minSdk = 28
targetSdk = 34
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
targetProjectPath = ":app"
flavorDimensions += listOf("api")
productFlavors {
create("default") { dimension = "api" }
create("marshmallow") { dimension = "api" }
}
testOptions.managedDevices.devices {
create<ManagedVirtualDevice>("pixel6Api34") {
device = "Pixel 6"
apiLevel = 34
systemImageSource = "aosp-atd"
}
}
}
baselineProfile {
managedDevices += "pixel6Api34"
useConnectedDevices = false
}
dependencies {
implementation(libs.androidx.junit)
implementation(libs.androidx.uiautomator)
implementation(libs.androidx.benchmark.macro.junit4)
}
androidComponents {
onVariants { v ->
val artifactsLoader = v.artifacts.getBuiltArtifactsLoader()
v.instrumentationRunnerArguments.put(
"targetAppId",
v.testedApks.map { artifactsLoader.load(it)!!.applicationId },
)
}
}
+1
View File
@@ -0,0 +1 @@
<manifest />
@@ -0,0 +1,51 @@
package com.ehviewer.baselineprofile
import androidx.benchmark.macro.junit4.BaselineProfileRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Direction
import androidx.test.uiautomator.Until
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {
@get:Rule
val rule = BaselineProfileRule()
@Test
fun generate() {
rule.collect(
packageName = InstrumentationRegistry.getArguments().getString("targetAppId")
?: throw Exception("targetAppId not passed as instrumentation runner arg"),
includeInStartupProfile = true,
) {
pressHome()
startActivityAndWait()
// Onboarding
if (device.hasObject(By.text("Username"))) {
device.findObject(By.scrollable(true))?.run {
scrollUntil(Direction.DOWN, Until.hasObject(By.text("Guest mode")))
}
device.findObject(By.text("Guest mode")).click()
}
// Gallery list
device.wait(Until.hasObject(By.text("NON-H")), 10_000)
device.findObject(By.scrollable(true))?.run {
fling(Direction.DOWN)
click()
}
// Gallery detail
device.wait(Until.hasObject(By.text("Similar")), 10_000)
device.findObject(By.hasDescendant(By.text("Read")).scrollable(true))?.fling(Direction.DOWN)
device.waitForIdle()
}
}
}
@@ -0,0 +1,44 @@
package com.ehviewer.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.platform.app.InstrumentationRegistry
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
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 = InstrumentationRegistry.getArguments().getString("targetAppId")
?: throw Exception("targetAppId not passed as instrumentation runner arg"),
metrics = listOf(StartupTimingMetric()),
compilationMode = compilationMode,
startupMode = StartupMode.COLD,
iterations = 10,
setupBlock = {
pressHome()
},
measureBlock = {
startActivityAndWait()
},
)
}
}
+2
View File
@@ -8,4 +8,6 @@ plugins {
alias(libs.plugins.spotless) apply false
alias(libs.plugins.aboutlibrariesPlugin) apply false
alias(libs.plugins.composeCompilerReportGenerator) apply false
alias(libs.plugins.android.test) apply false
alias(libs.plugins.baselineprofile) apply false
}
+13 -3
View File
@@ -31,6 +31,10 @@ okio = "3.9.0"
splitties = "3.0.0"
telephoto = "0.11.2"
xmlutil = "0.90.1"
junit = "1.2.0"
uiautomator = "2.3.0"
benchmark = "1.3.0-beta01"
profileinstaller = "1.4.0-alpha01"
[libraries]
aboutlibraries-core = { module = "com.mikepenz:aboutlibraries-core", version.ref = "aboutlibraries" }
@@ -110,8 +114,7 @@ serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbo
serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
serialization-json-okio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", version.ref = "kotlinx-serialization" }
serialization-xml-core = { module = "io.github.pdvrieze.xmlutil:core-android", version.ref = "xmlutil" }
serialization-xml = { module = "io.github.pdvrieze.xmlutil:serialization-android", version.ref = "xmlutil" }
serialization-xml = { module = "io.github.pdvrieze.xmlutil:serialization", version.ref = "xmlutil" }
splitties-appctx = { module = "com.louiscad.splitties:splitties-appctx", version.ref = "splitties" }
splitties-arch-room = { module = "com.louiscad.splitties:splitties-arch-room", version.ref = "splitties" }
@@ -120,10 +123,15 @@ splitties-systemservices = { module = "com.louiscad.splitties:splitties-systemse
telephoto-zoomable = { module = "me.saket.telephoto:zoomable", version.ref = "telephoto" }
androidx-junit = { module = "androidx.test.ext:junit", version.ref = "junit" }
androidx-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version.ref = "uiautomator" }
androidx-benchmark-macro-junit4 = { module = "androidx.benchmark:benchmark-macro-junit4", version.ref = "benchmark" }
androidx-profileinstaller = { module = "androidx.profileinstaller:profileinstaller", version.ref = "profileinstaller" }
[bundles]
coil = ["coil-compose", "coil-gif", "coil-network"]
compose = ["compose-foundation", "compose-material-icons-extended", "compose-material3", "compose-material3-adaptive", "compose-ui-util"]
kotlinx-serialization = ["serialization-cbor", "serialization-json", "serialization-json-okio", "serialization-xml-core", "serialization-xml"]
kotlinx-serialization = ["serialization-cbor", "serialization-json", "serialization-json-okio", "serialization-xml"]
splitties = ["splitties-appctx", "splitties-arch-room", "splitties-preferences", "splitties-systemservices"]
[plugins]
@@ -136,3 +144,5 @@ kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref =
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
spotless = { id = "com.diffplug.spotless", version = "6.25.0" }
android-test = { id = "com.android.test", version.ref = "agp" }
baselineprofile = { id = "androidx.baselineprofile", version.ref = "benchmark" }
+1
View File
@@ -28,3 +28,4 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
enableFeaturePreview("STABLE_CONFIGURATION_CACHE")
rootProject.name = "EhViewer"
include(":app")
include(":benchmark")