From 7ee0071da654ff60aa3aa19d213cbf854a51d622 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Tue, 13 Jan 2026 04:11:45 +0530 Subject: [PATCH] feat: restore baseline profile generation job --- android/build.gradle.kts | 9 +++ benchmark/.gitignore | 1 + benchmark/build.gradle.kts | 62 +++++++++++++++++ benchmark/src/main/AndroidManifest.xml | 11 +++ .../benchmark/BaselineProfileBenchmark.kt | 67 +++++++++++++++++++ .../benchmark/BaselineProfileGenerator.kt | 45 +++++++++++++ .../claw/android/benchmark/BenchmarkUtils.kt | 43 ++++++++++++ .../dev/msfjarvis/claw/gradle/SentryPlugin.kt | 1 + build.gradle.kts | 1 + gradle/libs.versions.toml | 16 +++++ settings.gradle.kts | 2 +- 11 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 benchmark/.gitignore create mode 100644 benchmark/build.gradle.kts create mode 100644 benchmark/src/main/AndroidManifest.xml create mode 100644 benchmark/src/main/kotlin/dev/msfjarvis/claw/android/benchmark/BaselineProfileBenchmark.kt create mode 100644 benchmark/src/main/kotlin/dev/msfjarvis/claw/android/benchmark/BaselineProfileGenerator.kt create mode 100644 benchmark/src/main/kotlin/dev/msfjarvis/claw/android/benchmark/BenchmarkUtils.kt diff --git a/android/build.gradle.kts b/android/build.gradle.kts index ee773ceb..647ab4ad 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -17,6 +17,7 @@ plugins { id("kotlin-parcelize") alias(libs.plugins.aboutlibraries) alias(libs.plugins.modulegraphassert) + alias(libs.plugins.baselineprofile) alias(libs.plugins.licensee) alias(libs.plugins.kotlin.composeCompiler) alias(libs.plugins.kotlin.serialization) @@ -34,10 +35,16 @@ android { signingConfig = signingConfigs["debug"] isMinifyEnabled = true } + buildTypes.getByName("release") { baselineProfile.automaticGenerationDuringBuild = true } } aboutLibraries.collect.gitHubApiToken = providers.environmentVariable("GITHUB_TOKEN").orNull +baselineProfile { + mergeIntoMain = true + saveInSrc = true +} + licensee { allow("Apache-2.0") allow("MIT") @@ -54,6 +61,8 @@ moduleGraphAssert { } dependencies { + baselineProfile(projects.benchmark) + implementation(platform(libs.okhttp.bom)) implementation(libs.aboutLibraries.compose.core) implementation(libs.aboutLibraries.core) diff --git a/benchmark/.gitignore b/benchmark/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/benchmark/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/benchmark/build.gradle.kts b/benchmark/build.gradle.kts new file mode 100644 index 00000000..41205ad2 --- /dev/null +++ b/benchmark/build.gradle.kts @@ -0,0 +1,62 @@ +/* + * Copyright © Harsh Shandilya. + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ +@file:Suppress("UnstableApiUsage") + +plugins { + alias(libs.plugins.android.test) + id("dev.msfjarvis.claw.kotlin-android") + alias(libs.plugins.baselineprofile) + alias(libs.plugins.dependencyAnalysis) +} + +android { + namespace = "dev.msfjarvis.claw.benchmark" + + compileSdk = 36 + defaultConfig { + minSdk = 28 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + create("benchmark") { + isDebuggable = true + signingConfig = signingConfigs["debug"] + matchingFallbacks += "release" + } + } + testOptions { + managedDevices { + localDevices.create("pixel6Api31") { + device = "Pixel 6" + apiLevel = 31 + systemImageSource = "aosp-atd" + } + } + } + targetProjectPath = ":android" +} + +baselineProfile { + managedDevices += "pixel6Api31" + useConnectedDevices = false + enableEmulatorDisplay = false +} + +dependencies { + implementation(libs.androidx.benchmark.macro.junit4) + implementation(libs.androidx.profileinstaller) + implementation(libs.androidx.test.core) + implementation(libs.androidx.test.espresso.core) + implementation(libs.androidx.test.ext.junit) + implementation(libs.androidx.test.rules) + implementation(libs.androidx.test.runner) + implementation(libs.androidx.test.uiautomator) + + implementation(libs.androidx.tracing.perfetto) + implementation(libs.androidx.tracing.perfetto.binary) +} diff --git a/benchmark/src/main/AndroidManifest.xml b/benchmark/src/main/AndroidManifest.xml new file mode 100644 index 00000000..ad7b85d3 --- /dev/null +++ b/benchmark/src/main/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/benchmark/src/main/kotlin/dev/msfjarvis/claw/android/benchmark/BaselineProfileBenchmark.kt b/benchmark/src/main/kotlin/dev/msfjarvis/claw/android/benchmark/BaselineProfileBenchmark.kt new file mode 100644 index 00000000..896bffc6 --- /dev/null +++ b/benchmark/src/main/kotlin/dev/msfjarvis/claw/android/benchmark/BaselineProfileBenchmark.kt @@ -0,0 +1,67 @@ +/* + * Copyright © Harsh Shandilya. + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ +package dev.msfjarvis.claw.android.benchmark + +import androidx.benchmark.macro.BaselineProfileMode +import androidx.benchmark.macro.CompilationMode +import androidx.benchmark.macro.FrameTimingMetric +import androidx.benchmark.macro.StartupMode +import androidx.benchmark.macro.StartupTimingMetric +import androidx.benchmark.macro.junit4.MacrobenchmarkRule +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class BaselineProfileBenchmark { + @get:Rule val benchmarkRule = MacrobenchmarkRule() + private lateinit var device: UiDevice + + @Before + fun setUp() { + val instrumentation = InstrumentationRegistry.getInstrumentation() + device = UiDevice.getInstance(instrumentation) + } + + @Test + fun noCompilation() { + exploreUI(CompilationMode.None()) + } + + @Test + fun fullyCompiled() { + exploreUI(CompilationMode.Full()) + } + + @Test + fun baselineProfile() { + exploreUI(CompilationMode.Partial(baselineProfileMode = BaselineProfileMode.Require)) + } + + private fun exploreUI(compilationMode: CompilationMode) { + benchmarkRule.measureRepeated( + packageName = PACKAGE_NAME, + metrics = listOf(FrameTimingMetric(), StartupTimingMetric()), + compilationMode = compilationMode, + startupMode = StartupMode.COLD, + iterations = 10, + ) { + device.executeShellCommand("pm clear $PACKAGE_NAME") + + startActivityAndWait() + + device.waitForIdle() + + device.savePosts() + + device.exploreScreens() + + device.returnToHottestScreen() + } + } +} diff --git a/benchmark/src/main/kotlin/dev/msfjarvis/claw/android/benchmark/BaselineProfileGenerator.kt b/benchmark/src/main/kotlin/dev/msfjarvis/claw/android/benchmark/BaselineProfileGenerator.kt new file mode 100644 index 00000000..0c9ba216 --- /dev/null +++ b/benchmark/src/main/kotlin/dev/msfjarvis/claw/android/benchmark/BaselineProfileGenerator.kt @@ -0,0 +1,45 @@ +/* + * Copyright © Harsh Shandilya. + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ +package dev.msfjarvis.claw.android.benchmark + +import androidx.benchmark.macro.junit4.BaselineProfileRule +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class BaselineProfileGenerator { + @get:Rule val baselineProfileRule = BaselineProfileRule() + private lateinit var device: UiDevice + + @Before + fun setUp() { + val instrumentation = InstrumentationRegistry.getInstrumentation() + device = UiDevice.getInstance(instrumentation) + } + + @Test + fun generateBaselineProfile() = + baselineProfileRule.collect( + packageName = PACKAGE_NAME, + maxIterations = 8, + includeInStartupProfile = true, + ) { + device.executeShellCommand("pm clear $PACKAGE_NAME") + + startActivityAndWait() + + device.waitForIdle() + + device.savePosts() + + device.exploreScreens() + + device.returnToHottestScreen() + } +} diff --git a/benchmark/src/main/kotlin/dev/msfjarvis/claw/android/benchmark/BenchmarkUtils.kt b/benchmark/src/main/kotlin/dev/msfjarvis/claw/android/benchmark/BenchmarkUtils.kt new file mode 100644 index 00000000..7142a8e6 --- /dev/null +++ b/benchmark/src/main/kotlin/dev/msfjarvis/claw/android/benchmark/BenchmarkUtils.kt @@ -0,0 +1,43 @@ +/* + * Copyright © Harsh Shandilya. + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ +package dev.msfjarvis.claw.android.benchmark + +import androidx.test.uiautomator.By +import androidx.test.uiautomator.BySelector +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.Until + +const val PACKAGE_NAME = "dev.msfjarvis.claw.android" +private const val AWAIT_TIMEOUT = 10_000L +private const val SAVE_BUTTON_ID = "save_button" +private const val NAV_ID_HOTTEST = "HOTTEST" +private const val NAV_ID_NEWEST = "NEWEST" +private const val NAV_ID_SAVED = "SAVED" + +fun UiDevice.savePosts() { + waitForObject(By.res(SAVE_BUTTON_ID)).click() +} + +fun UiDevice.exploreScreens() { + listOf(NAV_ID_HOTTEST, NAV_ID_NEWEST, NAV_ID_SAVED).forEach { tag -> + waitForObject(By.res(tag)).click() + waitForIdle() + } +} + +fun UiDevice.returnToHottestScreen() { + waitForObject(By.res(NAV_ID_HOTTEST)).click() +} + +private fun UiDevice.waitForObject(selector: BySelector, timeout: Long = AWAIT_TIMEOUT): UiObject2 { + if (wait(Until.hasObject(selector), timeout)) { + return findObject(selector) + } + + error("Object with selector [$selector] not found") +} diff --git a/build-logic/src/main/kotlin/dev/msfjarvis/claw/gradle/SentryPlugin.kt b/build-logic/src/main/kotlin/dev/msfjarvis/claw/gradle/SentryPlugin.kt index 9f5a7280..7735010d 100644 --- a/build-logic/src/main/kotlin/dev/msfjarvis/claw/gradle/SentryPlugin.kt +++ b/build-logic/src/main/kotlin/dev/msfjarvis/claw/gradle/SentryPlugin.kt @@ -41,6 +41,7 @@ class SentryPlugin : Plugin { autoUploadNativeSymbols.set(true) includeNativeSources.set(false) ignoredVariants.set(emptySet()) + ignoredBuildTypes.set(setOf("benchmark")) ignoredFlavors.set(emptySet()) tracingInstrumentation { enabled.set(true) diff --git a/build.gradle.kts b/build.gradle.kts index 0d4da7e3..292d520a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,5 +8,6 @@ plugins { id("dev.msfjarvis.claw.spotless") id("dev.msfjarvis.claw.versions") id("dev.msfjarvis.claw.kotlin-common") + alias(libs.plugins.android.test) apply false alias(libs.plugins.dependencyAnalysis) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3671c100..7f1aaeaa 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,7 @@ [versions] aboutLibraries = "14.0.0-a02" agp = "9.1.0-alpha04" +androidx-test = "1.7.0" annotation = "1.9.1" coil3 = "3.3.0" compose-animation = "1.11.0-alpha02" @@ -8,6 +9,7 @@ compose-foundation = "1.11.0-alpha02" compose-icons = "1.7.8" compose-material3 = "1.5.0-alpha11" compose-runtime = "1.11.0-alpha02" +benchmark = "1.5.0-alpha01" compose-ui = "1.11.0-alpha02" coroutines = "1.10.2" datastore = "1.2.0" @@ -28,6 +30,8 @@ sentry-sdk = "8.29.0" serialization = "1.10.0-RC" sqldelight = "2.2.1" sqlite = "2.6.2" +tracing = "1.3.0" +tracing-perfetto = "1.0.0" workmanager = "2.11.0" [libraries] @@ -40,6 +44,7 @@ android-security-lints = "com.android.security.lint:lint:1.0.4" androidx-activity = "androidx.activity:activity:1.12.2" androidx-activity-compose = "androidx.activity:activity-compose:1.12.2" androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" } +androidx-benchmark-macro-junit4 = { module = "androidx.benchmark:benchmark-macro-junit4", version.ref = "benchmark" } androidx-browser = "androidx.browser:browser:1.9.0" androidx-compose-animation = { module = "androidx.compose.animation:animation", version.ref = "compose-animation" } androidx-compose-animation-core = { module = "androidx.compose.animation:animation-core", version.ref = "compose-animation" } @@ -82,6 +87,15 @@ androidx-paging-common = { module = "androidx.paging:paging-common", version.ref androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "paging" } androidx-profileinstaller = "androidx.profileinstaller:profileinstaller:1.4.1" androidx-sqlite = { module = "androidx.sqlite:sqlite", version.ref = "sqlite" } +androidx-test-core = { module = "androidx.test:core", version.ref = "androidx-test" } +androidx-test-espresso-core = "androidx.test.espresso:espresso-core:3.7.0-alpha03" +androidx-test-ext-junit = "androidx.test.ext:junit:1.3.0-alpha03" +androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidx-test" } +androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test" } +androidx-test-uiautomator = "androidx.test.uiautomator:uiautomator:2.4.0-alpha04" +androidx-tracing = { group = "androidx.tracing", name = "tracing-ktx", version.ref = "tracing" } +androidx-tracing-perfetto = { module = "androidx.tracing:tracing-perfetto", version.ref = "tracing-perfetto" } +androidx-tracing-perfetto-binary = { module = "androidx.tracing:tracing-perfetto-binary", version.ref = "tracing-perfetto" } androidx-work-runtime = { module = "androidx.work:work-runtime", version.ref = "workmanager" } build-agp = { module = "com.android.tools.build:gradle", version.ref = "agp" } build-cachefix = "org.gradle.android.cache-fix:org.gradle.android.cache-fix.gradle.plugin:3.0.3" @@ -155,7 +169,9 @@ kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = [plugins] aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin.android", version.ref = "aboutLibraries" } android-lint = { id = "com.android.lint", version.ref = "agp" } +android-test = { id = "com.android.test", version.ref = "agp" } dependencyAnalysis = "com.autonomousapps.dependency-analysis:3.5.1" +baselineprofile = { id = "androidx.baselineprofile", version.ref = "benchmark" } kotlin-composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } ksp = "com.google.devtools.ksp:2.3.4" diff --git a/settings.gradle.kts b/settings.gradle.kts index ff225067..b8f67362 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -99,4 +99,4 @@ rootProject.name = "Claw" enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") -include("android", "api", "common", "core", "database:core", "database:impl", "model") +include("android", "api", "benchmark", "common", "core", "database:core", "database:impl", "model")