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() + } +}