From 27e2e3cc228a114f66a5da543dab872b7a584b15 Mon Sep 17 00:00:00 2001 From: Fredia Huya-Kouadio Date: Sat, 8 Jun 2024 13:01:35 -0700 Subject: [PATCH] Add macro benchmarking support for the Android editor --- platform/android/java/editor/build.gradle | 9 +++ .../java/editor/macrobenchmark/.gitignore | 1 + .../java/editor/macrobenchmark/build.gradle | 55 ++++++++++++++++++ .../src/main/AndroidManifest.xml | 1 + .../editor/macrobenchmark/EditorBenchmarks.kt | 57 +++++++++++++++++++ .../org/godotengine/editor/GodotEditor.kt | 8 ++- .../lib/src/org/godotengine/godot/Godot.kt | 3 + .../godotengine/godot/utils/BenchmarkUtils.kt | 6 +- platform/android/java/settings.gradle | 2 + 9 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 platform/android/java/editor/macrobenchmark/.gitignore create mode 100644 platform/android/java/editor/macrobenchmark/build.gradle create mode 100644 platform/android/java/editor/macrobenchmark/src/main/AndroidManifest.xml create mode 100644 platform/android/java/editor/macrobenchmark/src/main/java/org/godotengine/editor/macrobenchmark/EditorBenchmarks.kt diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle index 37f68d295ac2..4e073f1ca03c 100644 --- a/platform/android/java/editor/build.gradle +++ b/platform/android/java/editor/build.gradle @@ -12,6 +12,7 @@ dependencies { implementation "androidx.window:window:1.3.0" implementation "androidx.core:core-splashscreen:$versions.splashscreenVersion" implementation "androidx.constraintlayout:constraintlayout:2.1.4" + implementation "androidx.profileinstaller:profileinstaller:1.3.1" } ext { @@ -132,6 +133,14 @@ android { applicationIdSuffix ".dev" manifestPlaceholders += [editorBuildSuffix: " (dev)"] } + benchmark { + initWith release + applicationIdSuffix ".benchmark" + manifestPlaceholders += [editorBuildSuffix: " (benchmark)"] + signingConfig signingConfigs.debug + matchingFallbacks = ['release'] + debuggable false + } debug { initWith release diff --git a/platform/android/java/editor/macrobenchmark/.gitignore b/platform/android/java/editor/macrobenchmark/.gitignore new file mode 100644 index 000000000000..42afabfd2abe --- /dev/null +++ b/platform/android/java/editor/macrobenchmark/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/platform/android/java/editor/macrobenchmark/build.gradle b/platform/android/java/editor/macrobenchmark/build.gradle new file mode 100644 index 000000000000..adfdf7b41613 --- /dev/null +++ b/platform/android/java/editor/macrobenchmark/build.gradle @@ -0,0 +1,55 @@ +plugins { + id 'com.android.test' + id 'org.jetbrains.kotlin.android' +} + +android { + namespace 'org.godotengine.editor.macrobenchmark' + compileSdk versions.compileSdk + + compileOptions { + sourceCompatibility versions.javaVersion + targetCompatibility versions.javaVersion + } + + kotlinOptions { + jvmTarget = versions.javaVersion + } + + defaultConfig { + minSdk 24 + targetSdk versions.targetSdk + missingDimensionStrategy 'products', 'editor' + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = 'EMULATOR' + } + + buildTypes { + // This benchmark buildType is used for benchmarking, and should function like your + // release build (for example, with minification on). It's signed with a debug key + // for easy local/CI testing. + benchmark { + debuggable = true + signingConfig = debug.signingConfig + matchingFallbacks = ["release"] + } + } + + targetProjectPath = ":editor" + experimentalProperties["android.experimental.self-instrumenting"] = true +} + +dependencies { + implementation 'androidx.test:rules:1.5.0' + implementation 'androidx.test.ext:junit:1.1.5' + implementation 'androidx.test.espresso:espresso-core:3.5.1' + implementation 'androidx.test.uiautomator:uiautomator:2.3.0' + implementation 'androidx.benchmark:benchmark-macro-junit4:1.2.4' +} + +androidComponents { + beforeVariants(selector().all()) { + enable = buildType == "benchmark" + } +} diff --git a/platform/android/java/editor/macrobenchmark/src/main/AndroidManifest.xml b/platform/android/java/editor/macrobenchmark/src/main/AndroidManifest.xml new file mode 100644 index 000000000000..227314eeb7de --- /dev/null +++ b/platform/android/java/editor/macrobenchmark/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/platform/android/java/editor/macrobenchmark/src/main/java/org/godotengine/editor/macrobenchmark/EditorBenchmarks.kt b/platform/android/java/editor/macrobenchmark/src/main/java/org/godotengine/editor/macrobenchmark/EditorBenchmarks.kt new file mode 100644 index 000000000000..5fbe6e253551 --- /dev/null +++ b/platform/android/java/editor/macrobenchmark/src/main/java/org/godotengine/editor/macrobenchmark/EditorBenchmarks.kt @@ -0,0 +1,57 @@ +package org.godotengine.editor.macrobenchmark + +import android.Manifest +import androidx.benchmark.macro.ExperimentalMetricApi +import androidx.benchmark.macro.MemoryUsageMetric +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.rule.GrantPermissionRule +import androidx.test.uiautomator.By +import androidx.test.uiautomator.StaleObjectException +import androidx.test.uiautomator.Until +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Set of editor macro benchmarks. + * + * Before running, switch the editor's active build variant to 'benchmark' + */ +@RunWith(AndroidJUnit4::class) +class EditorBenchmarks { + + companion object { + const val PACKAGE_NAME = "org.godotengine.editor.v4.benchmark" + } + + @get:Rule val benchmarkRule = MacrobenchmarkRule() + @get:Rule val grantPermissionRule = GrantPermissionRule.grant( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE) + + /** + * Navigates to the device's home screen, and launches the Project Manager. + */ + @OptIn(ExperimentalMetricApi::class) + @Test + fun startupProjectManager() = benchmarkRule.measureRepeated( + packageName = PACKAGE_NAME, + metrics = listOf( + StartupTimingMetric(), + MemoryUsageMetric(MemoryUsageMetric.Mode.Max), + ), + iterations = 5, + startupMode = StartupMode.COLD + ) { + pressHome() + startActivityAndWait() + + try { + val editorLoadingIndicator = device.findObject(By.res(PACKAGE_NAME, "editor_loading_indicator")) + editorLoadingIndicator.wait(Until.gone(By.res(PACKAGE_NAME, "editor_loading_indicator")), 5_000) + } catch (ignored: StaleObjectException) {} + } +} diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt index 5515347bd61b..e70ddc7f2063 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt @@ -97,9 +97,11 @@ open class GodotEditor : GodotActivity() { override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() - // We exclude certain permissions from the set we request at startup, as they'll be - // requested on demand based on use-cases. - PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO)) + if (BuildConfig.BUILD_TYPE != "benchmark") { + // We exclude certain permissions from the set we request at startup, as they'll be + // requested on demand based on use-cases. + PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO)) + } val params = intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS) Log.d(TAG, "Starting intent $intent with parameters ${params.contentToString()}") diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt index 7e2a44ab390a..321e0660319e 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -656,6 +656,9 @@ class Godot(private val context: Context) : SensorEventListener { */ private fun onGodotMainLoopStarted() { Log.v(TAG, "OnGodotMainLoopStarted") + runOnUiThread { + getActivity()?.reportFullyDrawn() + } for (plugin in pluginRegistry.allPlugins) { plugin.onGodotMainLoopStarted() diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt b/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt index d39f2309b807..ebe740ef79a7 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt @@ -63,7 +63,7 @@ private val benchmarkTracker = Collections.synchronizedMap(LinkedHashMap