diff --git a/Source/Android/.gitignore b/Source/Android/.gitignore index 3fbf1ea2ff..2cef59b3ec 100644 --- a/Source/Android/.gitignore +++ b/Source/Android/.gitignore @@ -39,4 +39,5 @@ tasks.xml build/ *.so *.iml -build.properties \ No newline at end of file +build.properties +baseline-prof.txt diff --git a/Source/Android/app/build.gradle.kts b/Source/Android/app/build.gradle.kts index 86f2afe47d..f5b281690f 100644 --- a/Source/Android/app/build.gradle.kts +++ b/Source/Android/app/build.gradle.kts @@ -2,6 +2,7 @@ plugins { id("com.android.application") id("org.jetbrains.kotlin.android") kotlin("plugin.serialization") version "1.8.21" + id("androidx.baselineprofile") } @Suppress("UnstableApiUsage") @@ -115,6 +116,7 @@ android { } dependencies { + "baselineProfile"(project(":benchmark")) coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3") implementation("androidx.core:core-ktx:1.10.1") diff --git a/Source/Android/benchmark/.gitignore b/Source/Android/benchmark/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/Source/Android/benchmark/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/Source/Android/benchmark/build.gradle.kts b/Source/Android/benchmark/build.gradle.kts new file mode 100644 index 0000000000..0065dadb0f --- /dev/null +++ b/Source/Android/benchmark/build.gradle.kts @@ -0,0 +1,52 @@ +import com.android.build.api.dsl.ManagedVirtualDevice + +plugins { + id("com.android.test") + id("org.jetbrains.kotlin.android") + id("androidx.baselineprofile") +} + +android { + namespace = "org.dolphinemu.baselineprofile" + compileSdk = 34 + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = "17" + } + + defaultConfig { + minSdk = 28 + targetSdk = 34 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + targetProjectPath = ":app" + + testOptions.managedDevices.devices { + create("pixel6Api31") { + device = "Pixel 6" + apiLevel = 31 + 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 += "pixel6Api31" + useConnectedDevices = false +} + +dependencies { + implementation("androidx.test.ext:junit:1.1.5") + implementation("androidx.test.espresso:espresso-core:3.5.1") + implementation("androidx.test.uiautomator:uiautomator:2.2.0") + implementation("androidx.benchmark:benchmark-macro-junit4:1.2.2") +} diff --git a/Source/Android/benchmark/src/main/AndroidManifest.xml b/Source/Android/benchmark/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..cc947c5679 --- /dev/null +++ b/Source/Android/benchmark/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/Source/Android/benchmark/src/main/java/org/dolphinemu/baselineprofile/BaselineProfileGenerator.kt b/Source/Android/benchmark/src/main/java/org/dolphinemu/baselineprofile/BaselineProfileGenerator.kt new file mode 100644 index 0000000000..9bf621d6c1 --- /dev/null +++ b/Source/Android/benchmark/src/main/java/org/dolphinemu/baselineprofile/BaselineProfileGenerator.kt @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.baselineprofile + +import androidx.benchmark.macro.junit4.BaselineProfileRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import androidx.test.uiautomator.UiSelector +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("org.dolphinemu.dolphinemu") { + pressHome() + startActivityAndWait() + + // Dismiss analytics dialog due to issue with finding button + device.pressBack() + + // Navigate through activities that don't require games + // TODO: Make all activities testable without having games available (or use homebrew) + // TODO: Use resource strings to support more languages + + // Navigate to the Settings Activity + val settings = device.findObject(UiSelector().description("Settings")) + settings.clickAndWaitForNewWindow(30_000) + + // Go through settings and to the User Data Activity + val config = device.findObject(UiSelector().textContains("Config")) + config.click() + val userData = device.findObject(UiSelector().textContains("User Data")) + userData.clickAndWaitForNewWindow(30_000) + } + } +} diff --git a/Source/Android/benchmark/src/main/java/org/dolphinemu/baselineprofile/StartupBenchmarks.kt b/Source/Android/benchmark/src/main/java/org/dolphinemu/baselineprofile/StartupBenchmarks.kt new file mode 100644 index 0000000000..359a3df584 --- /dev/null +++ b/Source/Android/benchmark/src/main/java/org/dolphinemu/baselineprofile/StartupBenchmarks.kt @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.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 org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@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) { + rule.measureRepeated( + packageName = "org.dolphinemu.dolphinemu", + metrics = listOf(StartupTimingMetric()), + compilationMode = compilationMode, + startupMode = StartupMode.COLD, + iterations = 10, + setupBlock = { + pressHome() + }, + measureBlock = { + startActivityAndWait() + } + ) + } +} diff --git a/Source/Android/build.gradle.kts b/Source/Android/build.gradle.kts index 1ecafb96f1..730a0b458e 100644 --- a/Source/Android/build.gradle.kts +++ b/Source/Android/build.gradle.kts @@ -3,6 +3,8 @@ plugins { id("com.android.application") version "8.2.0" apply false id("com.android.library") version "8.2.0" apply false id("org.jetbrains.kotlin.android") version "1.8.21" apply false + id("com.android.test") version "8.2.0" apply false + id("androidx.baselineprofile") version "1.2.0" apply false } buildscript { diff --git a/Source/Android/settings.gradle.kts b/Source/Android/settings.gradle.kts index 1fb82df416..1a68f74a48 100644 --- a/Source/Android/settings.gradle.kts +++ b/Source/Android/settings.gradle.kts @@ -16,3 +16,4 @@ dependencyResolutionManagement { } include(":app") +include(":benchmark")