Refactor baseline profiles generation and regenerate (#3434)

This commit is contained in:
AntsyLich
2026-06-22 00:50:24 +06:00
committed by GitHub
parent 3bb734dde7
commit b3e190c627
20 changed files with 63270 additions and 37972 deletions
+50
View File
@@ -0,0 +1,50 @@
import com.android.build.api.dsl.ManagedVirtualDevice
plugins {
alias(mihonx.plugins.android.test)
alias(libs.plugins.androidx.baselineProfile)
}
android {
namespace = "mihon.baselineprofile"
defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
targetProjectPath = ":app"
// This code creates the gradle managed device used to generate baseline profiles.
// To use GMD please invoke generation through the command line:
// ./gradlew :app:generateBaselineProfile
testOptions.managedDevices.allDevices {
@Suppress("UnstableApiUsage")
create<ManagedVirtualDevice>("pixel6Api34") {
device = "Pixel 6"
apiLevel = 34
systemImageSource = "google"
}
}
}
// 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 {
managedDevices += "pixel6Api34"
useConnectedDevices = false
}
dependencies {
implementation(libs.androidx.benchmark.macroJunit4)
implementation(libs.androidx.test.junit)
implementation(libs.androidx.test.espresso.core)
implementation(libs.androidx.test.uiautomator)
}
androidComponents {
onVariants { variant ->
val artifactsLoader = variant.artifacts.getBuiltArtifactsLoader()
val applicationId = variant.testedApks.map { artifactsLoader.load(it)?.applicationId }
variant.instrumentationRunnerArguments.put("targetAppId", applicationId)
}
}
@@ -0,0 +1 @@
<manifest />
@@ -0,0 +1,46 @@
package mihon.baselineprofile
import androidx.benchmark.macro.junit4.BaselineProfileRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@LargeTest
class BaselineProfileGenerator {
@get:Rule
val rule = BaselineProfileRule()
@Test
fun generate() {
rule.collect(TARGET_PACKAGE_NAME) {
pressHome()
startActivityAndWait()
device.waitAndClick(By.text("Updates"))
device.waitForIdle()
device.waitAndClick(By.text("History"))
device.waitForIdle()
device.waitAndClick(By.text("Browse"))
device.waitForIdle()
device.waitAndClick(By.text("Extensions"))
device.waitForIdle()
device.waitAndClick(By.text("More"))
device.waitForIdle()
}
}
}
private fun UiDevice.waitAndClick(by: BySelector) {
wait(Until.findObject(by), 60_000).click()
}
@@ -0,0 +1,101 @@
package mihon.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.filters.LargeTest
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
/**
* Run this benchmark from Studio to see startup measurements, and captured system traces
* for investigating your app's performance from a cold state.
*/
@RunWith(AndroidJUnit4ClassRunner::class)
class ColdStartupBenchmark : AbstractStartupBenchmark(StartupMode.COLD)
/**
* Run this benchmark from Studio to see startup measurements, and captured system traces
* for investigating your app's performance from a warm state.
*/
@RunWith(AndroidJUnit4ClassRunner::class)
class WarmStartupBenchmark : AbstractStartupBenchmark(StartupMode.WARM)
/**
* Run this benchmark from Studio to see startup measurements, and captured system traces
* for investigating your app's performance from a hot state.
*/
@RunWith(AndroidJUnit4ClassRunner::class)
class HotStartupBenchmark : AbstractStartupBenchmark(StartupMode.HOT)
/**
* 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 :baselineprofile: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).
**/
abstract class AbstractStartupBenchmark(private val startupMode: StartupMode) {
@get:Rule
val rule = MacrobenchmarkRule()
@Test
fun startupCompilationNone() =
benchmark(CompilationMode.None())
@Test
fun startupCompilationBaselineProfilesDisabled() =
benchmark(CompilationMode.Partial(BaselineProfileMode.Disable))
@Test
fun startupCompilationBaselineProfiles() =
benchmark(CompilationMode.Partial(BaselineProfileMode.Require))
@Test
fun startupCompilationFull() = benchmark(CompilationMode.Full())
private fun benchmark(compilationMode: CompilationMode) {
// The application id for the running build variant is read from the instrumentation arguments.
rule.measureRepeated(
packageName = TARGET_PACKAGE_NAME,
metrics = listOf(StartupTimingMetric()),
compilationMode = compilationMode,
startupMode = startupMode,
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
}
)
}
}
@@ -0,0 +1,35 @@
package mihon.baselineprofile
import androidx.benchmark.macro.junit4.BaselineProfileRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@LargeTest
class StartupProfileGenerator {
@get:Rule
val rule = BaselineProfileRule()
@Test
fun generate() {
rule.collect(
packageName = TARGET_PACKAGE_NAME,
includeInStartupProfile = true
) {
pressHome()
startActivityAndWait()
}
}
}
private fun UiDevice.waitAndClick(by: BySelector) {
wait(Until.findObject(by), 60_000).click()
}
@@ -0,0 +1,7 @@
package mihon.baselineprofile
import androidx.test.platform.app.InstrumentationRegistry
internal val TARGET_PACKAGE_NAME: String
inline get() = InstrumentationRegistry.getArguments().getString("targetAppId")
?: throw Exception("targetAppId not passed as instrumentation runner arg")