From 84aad5f0941bf410a6dbd6e03072aceb66c3fa94 Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Fri, 27 Mar 2026 22:30:50 +0500 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8C=20macrobenchmark?= =?UTF-8?q?=20=D0=B8=20=D1=81=D1=86=D0=B5=D0=BD=D0=B0=D1=80=D0=B8=D0=B8=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BC=D0=B5=D1=80=D0=B0=20=D0=BF=D1=80=D0=BE=D0=B8?= =?UTF-8?q?=D0=B7=D0=B2=D0=BE=D0=B4=D0=B8=D1=82=D0=B5=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 10 +- baselineprofile/build.gradle.kts | 2 +- benchmark/README.md | 20 +++ benchmark/build.gradle.kts | 37 +++++ benchmark/src/main/AndroidManifest.xml | 2 + .../messenger/benchmark/AppMacrobenchmark.kt | 150 ++++++++++++++++++ settings.gradle.kts | 1 + 7 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 benchmark/README.md create mode 100644 benchmark/build.gradle.kts create mode 100644 benchmark/src/main/AndroidManifest.xml create mode 100644 benchmark/src/main/java/com/rosetta/messenger/benchmark/AppMacrobenchmark.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5aa981e..42b3dae 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -83,6 +83,14 @@ android { // Enable baseline profiles in debug builds too for testing // Remove this in production } + create("benchmark") { + initWith(getByName("release")) + signingConfig = signingConfigs.getByName("release") + matchingFallbacks += listOf("release") + isDebuggable = false + isMinifyEnabled = false + isShrinkResources = false + } } compileOptions { sourceCompatibility = JavaVersion.VERSION_11 @@ -192,7 +200,7 @@ dependencies { } // Baseline Profiles for startup performance - implementation("androidx.profileinstaller:profileinstaller:1.3.1") + implementation("androidx.profileinstaller:profileinstaller:1.4.1") // Firebase Cloud Messaging implementation(platform("com.google.firebase:firebase-bom:32.7.0")) diff --git a/baselineprofile/build.gradle.kts b/baselineprofile/build.gradle.kts index 6e477ef..01fab7f 100644 --- a/baselineprofile/build.gradle.kts +++ b/baselineprofile/build.gradle.kts @@ -29,5 +29,5 @@ 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") + implementation("androidx.benchmark:benchmark-macro-junit4:1.4.1") } diff --git a/benchmark/README.md b/benchmark/README.md new file mode 100644 index 0000000..6890ff8 --- /dev/null +++ b/benchmark/README.md @@ -0,0 +1,20 @@ +# Macrobenchmark + +Этот модуль запускает замеры производительности приложения `:app` на устройстве: + +- `coldStartup` — холодный запуск +- `chatListScroll` — прокрутка списка чатов +- `searchFlow` — вход в поиск и ввод запроса + +## Запуск + +```bash +./gradlew :benchmark:connectedCheck +``` + +Запуск только одного класса: + +```bash +./gradlew :benchmark:connectedAndroidTest \ + -Pandroid.testInstrumentationRunnerArguments.class=com.rosetta.messenger.benchmark.AppMacrobenchmark +``` diff --git a/benchmark/build.gradle.kts b/benchmark/build.gradle.kts new file mode 100644 index 0000000..8cf7eba --- /dev/null +++ b/benchmark/build.gradle.kts @@ -0,0 +1,37 @@ +plugins { + id("com.android.test") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "com.rosetta.messenger.benchmark" + compileSdk = 34 + + defaultConfig { + minSdk = 28 + targetSdk = 34 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArguments["androidx.benchmark.enabledRules"] = "Macrobenchmark" + testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = "EMULATOR,DEBUGGABLE" + testInstrumentationRunnerArguments["androidx.benchmark.compilation.enabled"] = "false" + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = "11" + } + + targetProjectPath = ":app" + experimentalProperties["android.experimental.self-instrumenting"] = true +} + +dependencies { + implementation("androidx.benchmark:benchmark-macro-junit4:1.4.1") + implementation("androidx.test.ext:junit:1.1.5") + implementation("androidx.test:runner:1.5.2") + implementation("androidx.test.uiautomator:uiautomator:2.2.0") +} diff --git a/benchmark/src/main/AndroidManifest.xml b/benchmark/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b2d3ea1 --- /dev/null +++ b/benchmark/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/benchmark/src/main/java/com/rosetta/messenger/benchmark/AppMacrobenchmark.kt b/benchmark/src/main/java/com/rosetta/messenger/benchmark/AppMacrobenchmark.kt new file mode 100644 index 0000000..8baa330 --- /dev/null +++ b/benchmark/src/main/java/com/rosetta/messenger/benchmark/AppMacrobenchmark.kt @@ -0,0 +1,150 @@ +package com.rosetta.messenger.benchmark + +import android.content.Intent +import androidx.benchmark.macro.CompilationMode +import androidx.benchmark.macro.FrameTimingMetric +import androidx.benchmark.macro.MacrobenchmarkScope +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.uiautomator.By +import androidx.test.uiautomator.Direction +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.Until +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@LargeTest +class AppMacrobenchmark { + + @get:Rule + val benchmarkRule = MacrobenchmarkRule() + + private val appPackage = "com.rosetta.messenger" + private val searchTriggers = listOf("Search", "search", "Поиск", "поиск", "Найти", "найти") + private val runtimePermissions = listOf( + "android.permission.POST_NOTIFICATIONS", + "android.permission.CAMERA", + "android.permission.RECORD_AUDIO", + "android.permission.READ_MEDIA_IMAGES", + "android.permission.READ_MEDIA_VIDEO", + "android.permission.BLUETOOTH_CONNECT" + ) + + @Test + fun coldStartup() { + benchmarkRule.measureRepeated( + packageName = appPackage, + metrics = listOf(StartupTimingMetric(), FrameTimingMetric()), + compilationMode = CompilationMode.Partial(), + startupMode = StartupMode.COLD, + iterations = 1, + setupBlock = { + grantRuntimePermissions() + pressHome() + } + ) { + launchMainActivity() + device.waitForIdle() + } + } + + @Test + fun chatListScroll() { + benchmarkRule.measureRepeated( + packageName = appPackage, + metrics = listOf(FrameTimingMetric()), + compilationMode = CompilationMode.Partial(), + iterations = 1, + setupBlock = { + prepareUi() + } + ) { + val list = device.findObject(By.scrollable(true)) ?: return@measureRepeated + repeat(2) { + list.safeScroll(Direction.DOWN, 1.0f) + device.waitForIdle() + list.safeScroll(Direction.UP, 1.0f) + device.waitForIdle() + } + } + } + + @Test + fun searchFlow() { + benchmarkRule.measureRepeated( + packageName = appPackage, + metrics = listOf(FrameTimingMetric()), + compilationMode = CompilationMode.Partial(), + iterations = 1, + setupBlock = { + prepareUi() + } + ) { + if (!openSearchInput()) return@measureRepeated + + val input = device.wait(Until.findObject(By.clazz("android.widget.EditText")), 1_500) + ?: return@measureRepeated + + input.text = "rosetta" + device.waitForIdle() + + device.findObject(By.scrollable(true))?.safeScroll(Direction.DOWN, 0.7f) + device.waitForIdle() + + device.pressBack() + device.waitForIdle() + } + } + + private fun MacrobenchmarkScope.prepareUi() { + grantRuntimePermissions() + pressHome() + launchMainActivity() + device.waitForIdle() + device.wait(Until.hasObject(By.pkg(appPackage).depth(0)), 3_000) + } + + private fun MacrobenchmarkScope.openSearchInput(): Boolean { + if (device.hasObject(By.clazz("android.widget.EditText"))) return true + + for (label in searchTriggers) { + val node = device.findObject(By.descContains(label)) ?: device.findObject(By.textContains(label)) + if (node != null) { + node.click() + device.waitForIdle() + if (device.wait(Until.hasObject(By.clazz("android.widget.EditText")), 1_000)) { + return true + } + } + } + + return device.hasObject(By.clazz("android.widget.EditText")) + } + + private fun UiObject2.safeScroll(direction: Direction, percent: Float) { + runCatching { scroll(direction, percent) } + } + + private fun MacrobenchmarkScope.grantRuntimePermissions() { + runtimePermissions.forEach { permission -> + runCatching { + device.executeShellCommand("pm grant $appPackage $permission") + } + } + } + + private fun MacrobenchmarkScope.launchMainActivity() { + val intent = Intent(Intent.ACTION_MAIN).apply { + addCategory(Intent.CATEGORY_LAUNCHER) + setClassName(appPackage, "$appPackage.MainActivity") + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) + } + startActivityAndWait(intent) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 09d1456..89679f1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,3 +19,4 @@ rootProject.name = "rosetta-android" include(":app") include(":baselineprofile") +include(":benchmark")