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:
1 parent
4dd6d68163
commit
cb2d99bdc1
13 files changed
+292
-12
No files matched your search
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -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 },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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" }
|
||||
@@ -28,3 +28,4 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
||||
enableFeaturePreview("STABLE_CONFIGURATION_CACHE")
|
||||
rootProject.name = "EhViewer"
|
||||
include(":app")
|
||||
include(":benchmark")
|
||||
Reference in new issue
Block a user