Skip to content

Commit

Permalink
add: baseline profiles
Browse files Browse the repository at this point in the history
  • Loading branch information
muedsa committed Jul 19, 2024
1 parent 9c6ac13 commit edcb59a
Show file tree
Hide file tree
Showing 13 changed files with 59,789 additions and 4 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,6 @@ lint/tmp/
*.keystore

# Google Services (e.g. APIs or Firebase)
google-services.json
google-services.json

.kotlin/
3 changes: 3 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ plugins {
alias(libs.plugins.firebaseCrashlytics)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.android.room)
alias(libs.plugins.baselineprofile)
}

val keystorePropertiesFile: File = rootProject.file("keystore.properties")
Expand Down Expand Up @@ -107,6 +108,8 @@ dependencies {
implementation(libs.lifecycle.runtime.compose)
implementation(libs.lifecycle.viewmodel.compose)
implementation(libs.hilt.android)
implementation(libs.profileinstaller)
baselineProfile(project(":benchmark"))
ksp(libs.hilt.compiler)
implementation(libs.activity.compose)
implementation(platform(libs.compose.bom))
Expand Down
29,749 changes: 29,749 additions & 0 deletions app/src/release/generated/baselineProfiles/baseline-prof.txt

Large diffs are not rendered by default.

29,749 changes: 29,749 additions & 0 deletions app/src/release/generated/baselineProfiles/startup-prof.txt

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions benchmark/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
52 changes: 52 additions & 0 deletions benchmark/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
plugins {
alias(libs.plugins.android.test)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.baselineprofile)
}

android {
namespace = "com.muedsa.jcytv.benchmark"
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"

}

// 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 {
useConnectedDevices = true
}

dependencies {
implementation(libs.junit)
implementation(libs.espresso.core)
implementation(libs.uiautomator)
implementation(libs.benchmark.macro.junit4)
}

androidComponents {
onVariants { v ->
val artifactsLoader = v.artifacts.getBuiltArtifactsLoader()
v.instrumentationRunnerArguments.put(
"targetAppId",
v.testedApks.map { artifactsLoader.load(it)?.applicationId }
)
}
}
1 change: 1 addition & 0 deletions benchmark/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<manifest />
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package com.muedsa.jcytv.benchmark

import androidx.benchmark.macro.junit4.BaselineProfileRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
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 in Android Studio or
* the equivalent `generateBaselineProfile` gradle task:
* ```
* ./gradlew :app:generateReleaseBaselineProfile
* ```
* 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.
*
* When using this class to generate a baseline profile, only API 33+ or rooted API 28+ are supported.
*
* The minimum required version of androidx.benchmark to generate a baseline profile is 1.2.0.
**/
@RunWith(AndroidJUnit4::class)
@LargeTest
class BaselineProfileGenerator {

@get:Rule
val rule = BaselineProfileRule()

@Test
fun generate() {
// The application id for the running build variant is read from the instrumentation arguments.
rule.collect(
packageName = InstrumentationRegistry.getArguments().getString("targetAppId")
?: throw Exception("targetAppId not passed as instrumentation runner arg"),

// See: https://d.android.com/topic/performance/baselineprofiles/dex-layout-optimizations
includeInStartupProfile = true
) {
// 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()

device.waitForIdle()
device.run {
// 等到首屏加载
wait(
Until.findObject(By.text("首页").clickable(true)),
INITIAL_WAIT_TIMEOUT
)
// 浏览首页 3个Row
repeat(3) {
pressDPadDown(); waitForIdle(WAIT_TIMEOUT)
repeat(10) { pressDPadRight(); waitForIdle(WAIT_TIMEOUT) }
repeat(10) { pressDPadLeft(); waitForIdle(WAIT_TIMEOUT) }
}
repeat(4) { pressDPadUp(); waitForIdle(WAIT_TIMEOUT) }

// 导航到排行
pressDPadRight(); waitForIdle(WAIT_TIMEOUT)
pressDPadCenter(); waitForIdle(WAIT_TIMEOUT)
// 等待排行页面加载完成
wait(
Until.findObject(By.textContains(". ")),
INITIAL_WAIT_TIMEOUT
)
// 浏览排行
repeat(5) { pressDPadDown(); waitForIdle(WAIT_TIMEOUT) }
pressDPadRight(); waitForIdle(WAIT_TIMEOUT)
repeat(5) { pressDPadUp(); waitForIdle(WAIT_TIMEOUT) }

// 导航到收藏
pressDPadRight(); waitForIdle(WAIT_TIMEOUT)
pressDPadCenter(); waitForIdle(WAIT_TIMEOUT)
// 等待收藏页面加载完成
wait(
Until.findObject(By.textContains("删除模式")),
INITIAL_WAIT_TIMEOUT
)
// 浏览收藏
pressDPadDown(); waitForIdle(WAIT_TIMEOUT)
pressDPadUp(); waitForIdle(WAIT_TIMEOUT)

// 导航到搜索
pressDPadRight(); waitForIdle(WAIT_TIMEOUT)
pressDPadCenter(); waitForIdle(WAIT_TIMEOUT)
// 等待搜索页面加载完成
wait(
Until.findObject(By.desc("搜索")),
INITIAL_WAIT_TIMEOUT
)

// 导航到目录
pressDPadRight(); waitForIdle(WAIT_TIMEOUT)
pressDPadCenter(); waitForIdle(WAIT_TIMEOUT)
// 等待目录加载完成
wait(
Until.findObject(By.textContains("更新")),
INITIAL_WAIT_TIMEOUT
)
// 浏览目录
pressDPadRight(); waitForIdle(WAIT_TIMEOUT)
pressDPadDown(); waitForIdle(WAIT_TIMEOUT)

// 进入视频详情
pressDPadCenter(); waitForIdle(WAIT_TIMEOUT)
// 等待视频详情页面加载完成
wait(
Until.findObject(By.textStartsWith("剧集 1-")),
INITIAL_WAIT_TIMEOUT
)
repeat(2) { pressDPadDown(); waitForIdle(WAIT_TIMEOUT) }
repeat(2) { pressDPadUp(); waitForIdle(WAIT_TIMEOUT) }
}
}
}
}

private const val INITIAL_WAIT_TIMEOUT = 5000L
private const val WAIT_TIMEOUT = 2000L
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.muedsa.jcytv.benchmark

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 androidx.test.platform.app.InstrumentationRegistry
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 for a variant, for example benchmarkRelease,
* with this Gradle task:
* ```
* ./gradlew :benchmark:connectedBenchmarkReleaseAndroidTest
* ```
*
* 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) {
// The application id for the running build variant is read from the instrumentation arguments.
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()

// 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
}
)
}
}
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ plugins {
alias(libs.plugins.firebaseCrashlytics) apply false
alias(libs.plugins.compose.compiler) apply false
alias(libs.plugins.android.room) apply false
alias(libs.plugins.android.test) apply false
alias(libs.plugins.baselineprofile) apply false
}
3 changes: 2 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
android.nonTransitiveRClass=true
org.gradle.configuration-cache=true
15 changes: 14 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ retrofit2-ktx-serialization = "1.0.0"
okhttp3-logging = "4.12.0"
room = "2.7.0-alpha05"
bcprov-jdk15to18 = "1.78.1"
junit = "1.2.1"
espresso-core = "3.5.1"
uiautomator = "2.3.0"
benchmark-macro-junit4 = "1.2.4"
baselineprofile = "1.2.4"
profileinstaller = "1.3.1"

[libraries]
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }
Expand Down Expand Up @@ -78,6 +84,11 @@ room = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
bcprov-jdk15to18 = { module = "org.bouncycastle:bcprov-jdk15to18", version.ref = "bcprov-jdk15to18" }
junit = { group = "androidx.test.ext", name = "junit", version.ref = "junit" }
espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" }
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]
android-application = { id = "com.android.application", version.ref = "agp" }
Expand All @@ -88,4 +99,6 @@ hiltAndroid = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
gmsGoogleService = { id = "com.google.gms.google-services", version.ref = "google-services" }
firebaseCrashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebase-crashlytics-gradle-plugin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
android-room = { id = "androidx.room", version.ref = "room" }
android-room = { id = "androidx.room", version.ref = "room" }
android-test = { id = "com.android.test", version.ref = "agp" }
baselineprofile = { id = "androidx.baselineprofile", version.ref = "baselineprofile" }
2 changes: 1 addition & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ dependencyResolutionManagement {

rootProject.name = "JCYTV"
include(":app")
include(":benchmark")

0 comments on commit edcb59a

Please sign in to comment.