Add FrameExperimentBenchmark
Bug: 322232828
Test: FrameExperimentBenchmark
Change-Id: Ie8ddb7c3cde481d92a0cc7f0df532cd83fec6f0c
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml b/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
index d56e2a2..1873374 100644
--- a/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
+++ b/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
@@ -91,6 +91,19 @@
</intent-filter>
</activity>
<activity
+ android:name=".FrameExperimentActivity"
+ android:label="FrameExp"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="androidx.compose.integration.macrobenchmark.target.FRAME_EXPERIMENT_ACTIVITY" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ <activity
android:name=".BaselineProfileActivity"
android:exported="true">
<intent-filter>
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/FrameExperimentActivity.kt b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/FrameExperimentActivity.kt
new file mode 100644
index 0000000..7c4f269
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/FrameExperimentActivity.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.integration.macrobenchmark.target
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.os.Bundle
+import android.os.Trace
+import android.view.View
+import androidx.activity.ComponentActivity
+
+private const val FRAME_ADDED_WORK_MS = 20L
+private const val FRAME_BASELINE_WORK_MS = 10L
+private const val FRAME_COUNT = 100
+
+// NOTE: Keep in sync with FrameExperimentBenchmark!!
+private enum class FrameMode(val id: Int) {
+ Fast(0),
+ PrefetchEveryFrame(1),
+ WorkDuringEveryFrame(2),
+ PrefetchSomeFrames(3),
+}
+
+private class FrameExperimentView(context: Context, val mode: FrameMode) : View(context) {
+
+ init {
+ setOnClickListener {
+ remainingFrames = FRAME_COUNT - 1
+ invalidate()
+ }
+ }
+ var remainingFrames = 0
+
+ fun work(durationMs: Long = FRAME_ADDED_WORK_MS, label: String = "Added item work") {
+ Trace.beginSection(label)
+
+ // spin!
+ val endTime = System.nanoTime() + durationMs * 1_000_000
+ @Suppress("ControlFlowWithEmptyBody")
+ while (System.nanoTime() < endTime) {}
+
+ Trace.endSection()
+ }
+
+ val paintA = Paint().apply { setColor(Color.LTGRAY) }
+ val paintB = Paint().apply { setColor(Color.WHITE) }
+
+ override fun onDraw(canvas: Canvas) {
+ if (mode == FrameMode.WorkDuringEveryFrame) {
+ work()
+ }
+ super.onDraw(canvas)
+
+ work(durationMs = FRAME_BASELINE_WORK_MS, "Baseline work frame $remainingFrames")
+
+ // small rect to reduce flicker
+ canvas.drawRect(
+ 0f, 0f, 200f, 200f,
+ if (remainingFrames % 2 == 0) paintA else paintB
+ )
+
+ if (remainingFrames >= 1) {
+ remainingFrames--
+ invalidate()
+
+ if (mode == FrameMode.PrefetchEveryFrame ||
+ (mode == FrameMode.PrefetchSomeFrames && remainingFrames % 5 == 0)) {
+ this.post {
+ work()
+ }
+ }
+ }
+ }
+}
+
+class FrameExperimentActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val frameModeId = intent.getIntExtra(EXTRA_FRAME_MODE, defaultMode.id)
+ val frameMode = FrameMode.values().first { it.id == frameModeId }
+
+ setContentView(
+ FrameExperimentView(this, frameMode)
+ )
+ }
+
+ companion object {
+ const val EXTRA_FRAME_MODE = "FRAME_MODE"
+ private val defaultMode = FrameMode.Fast
+ }
+}
diff --git a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/FrameExperimentBenchmark.kt b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/FrameExperimentBenchmark.kt
new file mode 100644
index 0000000..de91407
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/FrameExperimentBenchmark.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.integration.macrobenchmark
+
+import android.content.Intent
+import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.FrameTimingMetric
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.testutils.createCompilationParams
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Benchmark for experimenting with synthetic frame patterns/durations and how
+ * they show up in metrics
+ */
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class FrameExperimentBenchmark {
+ @get:Rule
+ val benchmarkRule = MacrobenchmarkRule()
+
+ // NOTE: Keep in sync with FrameExperimentActivity!!
+ private enum class FrameMode(val id: Int) {
+ Fast(0),
+ PrefetchEveryFrame(1),
+ WorkDuringEveryFrame(2),
+ PrefetchSomeFrames(3),
+ }
+
+ @Test
+ fun fast() = benchmark(FrameMode.Fast)
+ @Test
+ fun prefetchEveryFrame() = benchmark(FrameMode.PrefetchEveryFrame)
+ @Test
+ fun workDuringEveryFrame() = benchmark(FrameMode.WorkDuringEveryFrame)
+ @Test
+ fun prefetchSomeFrames() = benchmark(FrameMode.PrefetchSomeFrames)
+
+ private fun benchmark(mode: FrameMode) {
+ benchmarkRule.measureRepeated(
+ packageName = PACKAGE_NAME,
+ metrics = listOf(FrameTimingMetric()),
+ compilationMode = CompilationMode.DEFAULT,
+ iterations = 1,
+ setupBlock = {
+ val intent = Intent()
+ intent.action = ACTION
+ intent.putExtra("FRAME_MODE", mode.id)
+ startActivityAndWait(intent)
+ }
+ ) {
+ device.click(device.displayWidth / 2, device.displayHeight / 2)
+ Thread.sleep(4_000) // empirically enough to produce expected frames
+ }
+ }
+
+ companion object {
+ private const val PACKAGE_NAME = "androidx.compose.integration.macrobenchmark.target"
+ private const val ACTION =
+ "androidx.compose.integration.macrobenchmark.target.FRAME_EXPERIMENT_ACTIVITY"
+
+ @Parameterized.Parameters(name = "compilation={0}")
+ @JvmStatic
+ fun parameters() = createCompilationParams()
+ }
+}