From cfbd6dbce621b0cb4fdd1b7626490e45d988a9be Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Fri, 1 Mar 2024 19:22:17 +0000 Subject: [PATCH 1/3] Add a starting point for benchmarks --- app-watch-benchmark/.gitignore | 1 + app-watch-benchmark/build.gradle.kts | 53 ++++++++++++++ .../src/main/AndroidManifest.xml | 13 ++++ .../benchmark/DeviceUtils.kt | 41 +++++++++++ .../benchmark/HansieWatchfaceBenchmark.kt | 10 +++ .../benchmark/IgnoredMetric.kt | 17 +++++ .../benchmark/WatchFaceBenchmark.kt | 72 +++++++++++++++++++ app-watch/src/main/AndroidManifest.xml | 29 ++++++++ .../kotlin/samples/HansieWatchFaceService.kt | 24 +++++++ settings.gradle.kts | 1 + versions.properties | 9 +++ 11 files changed, 270 insertions(+) create mode 100644 app-watch-benchmark/.gitignore create mode 100644 app-watch-benchmark/build.gradle.kts create mode 100644 app-watch-benchmark/src/main/AndroidManifest.xml create mode 100644 app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/DeviceUtils.kt create mode 100644 app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/HansieWatchfaceBenchmark.kt create mode 100644 app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/IgnoredMetric.kt create mode 100644 app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/WatchFaceBenchmark.kt create mode 100644 app-watch/src/main/kotlin/samples/HansieWatchFaceService.kt diff --git a/app-watch-benchmark/.gitignore b/app-watch-benchmark/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app-watch-benchmark/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app-watch-benchmark/build.gradle.kts b/app-watch-benchmark/build.gradle.kts new file mode 100644 index 0000000..2909409 --- /dev/null +++ b/app-watch-benchmark/build.gradle.kts @@ -0,0 +1,53 @@ +@file:Suppress("UnstableApiUsage") + +plugins { + kotlin("android") + id("com.android.test") +} + +android { + namespace = "com.louiscad.composeoclockplayground.benchmark" + + defaultConfig { + minSdk = 30 + compileSdk = 34 + targetSdk = 34 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + 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. + val benchmark by creating { + isDebuggable = false + signingConfig = signingConfigs.getByName("debug") + matchingFallbacks.add("release") + } + } + + targetProjectPath = ":app-watch" + experimentalProperties["android.experimental.self-instrumenting"] = true +} + +dependencies { + implementation { + platform(AndroidX.compose.bom) + AndroidX.compose.ui.testJunit4() + AndroidX.test.runner() + AndroidX.test.rules() + AndroidX.test.uiAutomator() + AndroidX.benchmark.macroJunit4() + } +} + +androidComponents { + beforeVariants { + it.enable = it.buildType == "benchmark" + } +} diff --git a/app-watch-benchmark/src/main/AndroidManifest.xml b/app-watch-benchmark/src/main/AndroidManifest.xml new file mode 100644 index 0000000..4bacb4f --- /dev/null +++ b/app-watch-benchmark/src/main/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/DeviceUtils.kt b/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/DeviceUtils.kt new file mode 100644 index 0000000..7942f4b --- /dev/null +++ b/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/DeviceUtils.kt @@ -0,0 +1,41 @@ +package com.louiscad.composeoclockplayground.benchmark + +import android.app.UiAutomation +import android.content.ComponentName +import android.view.KeyEvent +import androidx.test.uiautomator.UiDevice + +fun UiDevice.startWatchface(watchfaceName: ComponentName) { + // From https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:deploy/deployer/src/main/java/com/android/tools/deployer/model/component/WatchFace.java + val result = + executeShellCommand("am broadcast -a com.google.android.wearable.app.DEBUG_SURFACE --es operation set-watchface --ecn component ${watchfaceName.flattenToString()}") + + // TODO error checking +} + +fun UiDevice.currentWatchface(): String { + return executeShellCommand("am broadcast -a com.google.android.wearable.app.DEBUG_SURFACE --es operation current-watchface") +} + +val DefaultWatchFace = ComponentName( + "com.google.android.wearable.sysui", + "com.google.android.clockwork.sysui.experiences.defaultwatchface.DefaultWatchFace" +) + +inline fun UiAutomation.withShellPermission(block: () -> Unit) { + adoptShellPermissionIdentity() + + try { + block() + } finally { + dropShellPermissionIdentity() + } +} + +fun UiDevice.pressSleep() { + pressKeyCode(KeyEvent.KEYCODE_SLEEP) +} + +fun UiDevice.pressWakeup() { + pressKeyCode(KeyEvent.KEYCODE_WAKEUP) +} diff --git a/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/HansieWatchfaceBenchmark.kt b/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/HansieWatchfaceBenchmark.kt new file mode 100644 index 0000000..3296380 --- /dev/null +++ b/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/HansieWatchfaceBenchmark.kt @@ -0,0 +1,10 @@ +package com.louiscad.composeoclockplayground.benchmark + +import android.content.ComponentName + +class HansieWatchfaceBenchmark : WatchFaceBenchmark() { + override val watchfaceComponent: ComponentName = ComponentName( + PACKAGE_NAME, + "com.louiscad.composeoclockplayground.samples.HansieWatchFaceService" + ) +} \ No newline at end of file diff --git a/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/IgnoredMetric.kt b/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/IgnoredMetric.kt new file mode 100644 index 0000000..b2d712b --- /dev/null +++ b/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/IgnoredMetric.kt @@ -0,0 +1,17 @@ +@file:OptIn(ExperimentalMetricApi::class, ExperimentalPerfettoTraceProcessorApi::class) + +package com.louiscad.composeoclockplayground.benchmark + +import androidx.benchmark.macro.ExperimentalMetricApi +import androidx.benchmark.macro.TraceMetric +import androidx.benchmark.perfetto.ExperimentalPerfettoTraceProcessorApi +import androidx.benchmark.perfetto.PerfettoTraceProcessor + +object IgnoredMetric : TraceMetric() { + override fun getResult( + captureInfo: CaptureInfo, + traceSession: PerfettoTraceProcessor.Session + ): List { + return listOf(Measurement("ignored", 0.0)) + } +} \ No newline at end of file diff --git a/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/WatchFaceBenchmark.kt b/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/WatchFaceBenchmark.kt new file mode 100644 index 0000000..9384102 --- /dev/null +++ b/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/WatchFaceBenchmark.kt @@ -0,0 +1,72 @@ +package com.louiscad.composeoclockplayground.benchmark + +import android.app.Instrumentation +import android.app.UiAutomation +import android.content.ComponentName +import androidx.benchmark.macro.CompilationMode +import androidx.benchmark.macro.StartupMode +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 + +abstract class WatchFaceBenchmark { + private lateinit var instrumentation: Instrumentation + private lateinit var uiAutomation: UiAutomation + private lateinit var device: UiDevice + + abstract val watchfaceComponent: ComponentName + + @get:Rule + val benchmarkRule = MacrobenchmarkRule() + + @Before + fun setup() { + instrumentation = InstrumentationRegistry.getInstrumentation() + uiAutomation = instrumentation.uiAutomation + device = UiDevice.getInstance(instrumentation) + } + + @Before + fun after() { + device.wakeUp() + device.startWatchface(DefaultWatchFace) + } + + @Test + fun oneMinuteInteractive() = benchmarkRule.measureRepeated( + packageName = watchfaceComponent.packageName, + metrics = metrics(), + compilationMode = CompilationMode.Partial(), + // TODO bump to multiple + iterations = 1, + startupMode = StartupMode.WARM, + setupBlock = { + device.startWatchface(watchfaceComponent) + repeat(5) { + println("Sleep in setupBlock $it") + Thread.sleep(1000) + } + + println("Waking Up") + device.wakeUp() + }, + ) { + repeat(10) { + device.wakeUp() + println("Sleep in test $it") + Thread.sleep(6000) + } + } + + // TODO add interesting watchface metrics + open fun metrics() = listOf(IgnoredMetric) + + companion object { + val PACKAGE_NAME = "com.louiscad.composeoclockplayground" + } +} + + diff --git a/app-watch/src/main/AndroidManifest.xml b/app-watch/src/main/AndroidManifest.xml index a6d858c..c26bee1 100644 --- a/app-watch/src/main/AndroidManifest.xml +++ b/app-watch/src/main/AndroidManifest.xml @@ -71,6 +71,35 @@ + + + + + + + + + + + + + + + + diff --git a/app-watch/src/main/kotlin/samples/HansieWatchFaceService.kt b/app-watch/src/main/kotlin/samples/HansieWatchFaceService.kt new file mode 100644 index 0000000..b3f6cf0 --- /dev/null +++ b/app-watch/src/main/kotlin/samples/HansieWatchFaceService.kt @@ -0,0 +1,24 @@ +package com.louiscad.composeoclockplayground.samples + +import androidx.compose.runtime.Composable +import androidx.wear.watchface.complications.data.ComplicationData +import androidx.wear.watchface.complications.data.ComplicationType +import kotlinx.coroutines.flow.StateFlow +import org.splitties.compose.oclock.ComposeWatchFaceService +import org.splitties.compose.oclock.sample.watchfaces.hansie.HansieClock + +class HansieWatchFaceService : ComposeWatchFaceService( + complicationSlotIds = emptySet(), + invalidationMode = InvalidationMode.WaitForInvalidation +) { + + @Composable + override fun Content(complicationData: Map>) { + HansieClock() + } + + override fun supportedComplicationTypes(slotId: Int) = listOf( + ComplicationType.RANGED_VALUE, + ComplicationType.SHORT_TEXT + ) +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 25dceae..df9f474 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -46,4 +46,5 @@ include { "app-phone"() "app-watch"() "shared"() + "app-watch-benchmark"() } diff --git a/versions.properties b/versions.properties index e5db9f6..c143ded 100644 --- a/versions.properties +++ b/versions.properties @@ -19,6 +19,8 @@ version.androidx.activity=1.8.2 ## # available=1.9.0-alpha01 ## # available=1.9.0-alpha02 +version.androidx.benchmark=1.3.0-alpha01 + version.androidx.compose=2023.10.01 version.androidx.compose.compiler=1.5.8 @@ -72,6 +74,11 @@ version.androidx.test.ext.junit=1.1.5 ## # available=1.2.0-alpha02 ## # available=1.2.0-alpha03 +version.androidx.test.rules=1.5.0 +## # available=1.6.0-alpha01 +## # available=1.6.0-alpha02 +## # available=1.6.0-alpha03 + version.androidx.test.runner=1.5.2 ## # available=1.5.3-alpha01 ## # available=1.6.0-alpha01 @@ -81,6 +88,8 @@ version.androidx.test.runner=1.5.2 ## # available=1.6.0-alpha05 ## # available=1.6.0-alpha06 +version.androidx.test.uiautomator=2.3.0 + version.androidx.wear.compose=1.3.0 ## # available=1.4.0-alpha01 From 387fb34014399242d7b7051ced65957ee097625d Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Fri, 1 Mar 2024 19:27:39 +0000 Subject: [PATCH 2/3] Add a starting point for benchmarks --- app-watch/proguard-benchmark.pro | 3 +++ convention-plugins/src/main/kotlin/android-app.gradle.kts | 8 ++++++++ 2 files changed, 11 insertions(+) create mode 100644 app-watch/proguard-benchmark.pro diff --git a/app-watch/proguard-benchmark.pro b/app-watch/proguard-benchmark.pro new file mode 100644 index 0000000..4ce9fce --- /dev/null +++ b/app-watch/proguard-benchmark.pro @@ -0,0 +1,3 @@ +# When generating the baseline profile we want the proper names of +# the methods and classes +-dontobfuscate \ No newline at end of file diff --git a/convention-plugins/src/main/kotlin/android-app.gradle.kts b/convention-plugins/src/main/kotlin/android-app.gradle.kts index b5a040b..5934556 100644 --- a/convention-plugins/src/main/kotlin/android-app.gradle.kts +++ b/convention-plugins/src/main/kotlin/android-app.gradle.kts @@ -58,6 +58,14 @@ android { "proguard-rules.pro" ) } + create("benchmark") { + initWith(buildTypes.getByName("release")) + matchingFallbacks += "release" + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-benchmark.pro" + ) + } } kotlinOptions { freeCompilerArgs += "-opt-in=splitties.experimental.ExperimentalSplittiesApi" From da508a59eae208874bacf379f0cb2c40e831cb8c Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sat, 2 Mar 2024 11:05:33 +0000 Subject: [PATCH 3/3] Simplify --- app-watch-benchmark/build.gradle.kts | 1 + ...nchmark.kt => SampleWatchfaceBenchmark.kt} | 4 +-- app-watch/src/main/AndroidManifest.xml | 29 ------------------- .../kotlin/samples/HansieWatchFaceService.kt | 24 --------------- 4 files changed, 3 insertions(+), 55 deletions(-) rename app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/{HansieWatchfaceBenchmark.kt => SampleWatchfaceBenchmark.kt} (58%) delete mode 100644 app-watch/src/main/kotlin/samples/HansieWatchFaceService.kt diff --git a/app-watch-benchmark/build.gradle.kts b/app-watch-benchmark/build.gradle.kts index 2909409..f7bf90a 100644 --- a/app-watch-benchmark/build.gradle.kts +++ b/app-watch-benchmark/build.gradle.kts @@ -14,6 +14,7 @@ android { targetSdk = 34 testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArguments["androidx.benchmark.profiling.mode"] = "none" } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 diff --git a/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/HansieWatchfaceBenchmark.kt b/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/SampleWatchfaceBenchmark.kt similarity index 58% rename from app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/HansieWatchfaceBenchmark.kt rename to app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/SampleWatchfaceBenchmark.kt index 3296380..8a397e0 100644 --- a/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/HansieWatchfaceBenchmark.kt +++ b/app-watch-benchmark/src/main/kotlin/com/louiscad/composeoclockplayground/benchmark/SampleWatchfaceBenchmark.kt @@ -2,9 +2,9 @@ package com.louiscad.composeoclockplayground.benchmark import android.content.ComponentName -class HansieWatchfaceBenchmark : WatchFaceBenchmark() { +class SampleWatchfaceBenchmark : WatchFaceBenchmark() { override val watchfaceComponent: ComponentName = ComponentName( PACKAGE_NAME, - "com.louiscad.composeoclockplayground.samples.HansieWatchFaceService" + "com.louiscad.composeoclockplayground.SampleWatchFaceService" ) } \ No newline at end of file diff --git a/app-watch/src/main/AndroidManifest.xml b/app-watch/src/main/AndroidManifest.xml index c26bee1..a6d858c 100644 --- a/app-watch/src/main/AndroidManifest.xml +++ b/app-watch/src/main/AndroidManifest.xml @@ -71,35 +71,6 @@ - - - - - - - - - - - - - - - - diff --git a/app-watch/src/main/kotlin/samples/HansieWatchFaceService.kt b/app-watch/src/main/kotlin/samples/HansieWatchFaceService.kt deleted file mode 100644 index b3f6cf0..0000000 --- a/app-watch/src/main/kotlin/samples/HansieWatchFaceService.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.louiscad.composeoclockplayground.samples - -import androidx.compose.runtime.Composable -import androidx.wear.watchface.complications.data.ComplicationData -import androidx.wear.watchface.complications.data.ComplicationType -import kotlinx.coroutines.flow.StateFlow -import org.splitties.compose.oclock.ComposeWatchFaceService -import org.splitties.compose.oclock.sample.watchfaces.hansie.HansieClock - -class HansieWatchFaceService : ComposeWatchFaceService( - complicationSlotIds = emptySet(), - invalidationMode = InvalidationMode.WaitForInvalidation -) { - - @Composable - override fun Content(complicationData: Map>) { - HansieClock() - } - - override fun supportedComplicationTypes(slotId: Int) = listOf( - ComplicationType.RANGED_VALUE, - ComplicationType.SHORT_TEXT - ) -}