-
Notifications
You must be signed in to change notification settings - Fork 331
Bug 1807324 - Add macrobenchmarks for startup metrics #842
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
plugins { | ||
id 'com.android.test' | ||
id 'org.jetbrains.kotlin.android' | ||
} | ||
|
||
android { | ||
namespace 'org.mozilla.fenix.benchmark' | ||
compileSdk Config.compileSdkVersion | ||
|
||
compileOptions { | ||
sourceCompatibility = JavaVersion.VERSION_11 | ||
targetCompatibility = JavaVersion.VERSION_11 | ||
} | ||
|
||
kotlinOptions { | ||
jvmTarget = "11" | ||
} | ||
|
||
defaultConfig { | ||
minSdk 23 | ||
targetSdk Config.targetSdkVersion | ||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | ||
} | ||
|
||
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 testing. | ||
benchmark { | ||
debuggable = true | ||
signingConfig signingConfigs.debug | ||
matchingFallbacks = ["release"] | ||
} | ||
} | ||
|
||
targetProjectPath = ":app" | ||
experimentalProperties["android.experimental.self-instrumenting"] = true | ||
} | ||
|
||
/** | ||
* This fixes the dependency resolution issue with Glean Native. The glean gradle plugin does this | ||
* and that's applied to the app module. Since there are no other uses of the glean plugin in the | ||
* benchmark module, we do this manually here. | ||
*/ | ||
configurations.all { | ||
resolutionStrategy.capabilitiesResolution.withCapability("org.mozilla.telemetry:glean-native") { | ||
def toBeSelected = candidates.find { it.id instanceof ModuleComponentIdentifier && it.id.module.contains('geckoview') } | ||
if (toBeSelected != null) { | ||
select(toBeSelected) | ||
} | ||
because 'use GeckoView Glean instead of standalone Glean' | ||
} | ||
} | ||
Comment on lines
+44
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't really understand what's happening here, would you mind explaining it to me? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a build error because of glean dependency resolution conflict as it was coming from geckoview as well. The glean plugin applied on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Writing it out helps me remember better, so this is for myself really! In a regular GeckoView build we have Glean baked in because there is a client within Gecko already so we really want to use that. So when build dependency resolution happens, we want to tell gradle "choose this one, don't worry": graph TD
A[GeckoView + Glean] --> B[Fenix APK]
C[Rust Components] --> B
In a GeckoView Lite build, which third party consumers may use because they don't want Mozilla telemetry in it, they could still use a separate compiled component for Glean but that would mean there is a large amount of duplication in there, or they have to figure out how to deliver telemetry to and fro from the engine to this separate component (which the path we didn't take because it's quite a bit more complicated): graph TD
A[GeckoView] --> B[Fenix APK]
C[Rust Components] --> B
D["Glean Component (optional)"] --> B
|
||
|
||
dependencies { | ||
implementation FenixDependencies.androidx_junit | ||
implementation FenixDependencies.espresso_core | ||
implementation FenixDependencies.uiautomator | ||
implementation FenixDependencies.androidx_benchmark_macro_junit4 | ||
} | ||
|
||
androidComponents { | ||
beforeVariants(selector().all()) { | ||
enabled = buildType == "benchmark" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> | ||
|
||
<queries> | ||
<package android:name="org.mozilla.fenix" /> | ||
</queries> | ||
</manifest> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
package org.mozilla.fenix.benchmark | ||
|
||
import androidx.benchmark.macro.StartupMode | ||
import androidx.benchmark.macro.StartupTimingMetric | ||
import androidx.benchmark.macro.junit4.MacrobenchmarkRule | ||
import androidx.test.ext.junit.runners.AndroidJUnit4 | ||
import org.junit.Rule | ||
import org.junit.Test | ||
import org.junit.runner.RunWith | ||
import org.mozilla.fenix.benchmark.utils.measureRepeatedDefault | ||
|
||
/** | ||
* This is a startup benchmark. | ||
* It navigates to the device's home screen, and launches the default activity. | ||
* | ||
* Before running this benchmark, | ||
* switch your app's active build variant in the Studio (affects Studio runs only) | ||
* | ||
* Run this benchmark from Studio to see startup measurements, and captured system traces | ||
* for investigating your app's performance. | ||
*/ | ||
@RunWith(AndroidJUnit4::class) | ||
class StartupBenchmark { | ||
@get:Rule | ||
val benchmarkRule = MacrobenchmarkRule() | ||
|
||
@Test | ||
fun startupCold() = startupBenchmark(StartupMode.COLD) | ||
|
||
@Test | ||
fun startupWarm() = startupBenchmark(StartupMode.WARM) | ||
|
||
@Test | ||
fun startupHot() = startupBenchmark(StartupMode.HOT) | ||
|
||
private fun startupBenchmark(startupMode: StartupMode) = benchmarkRule.measureRepeatedDefault( | ||
metrics = listOf(StartupTimingMetric()), | ||
startupMode = startupMode, | ||
setupBlock = { | ||
pressHome() | ||
} | ||
) { | ||
startActivityAndWait() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
package org.mozilla.fenix.benchmark.utils | ||
|
||
const val TARGET_PACKAGE = "org.mozilla.fenix" | ||
const val DEFAULT_ITERATIONS = 5 | ||
MatthewTighe marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
package org.mozilla.fenix.benchmark.utils | ||
|
||
import androidx.annotation.IntRange | ||
import androidx.benchmark.macro.CompilationMode | ||
import androidx.benchmark.macro.MacrobenchmarkScope | ||
import androidx.benchmark.macro.Metric | ||
import androidx.benchmark.macro.StartupMode | ||
import androidx.benchmark.macro.junit4.MacrobenchmarkRule | ||
|
||
/** | ||
* Extension function that calls [MacrobenchmarkRule.measureRepeated] with | ||
* defaults parameters set for [packageName] and [iterations]. | ||
*/ | ||
fun MacrobenchmarkRule.measureRepeatedDefault( | ||
packageName: String = TARGET_PACKAGE, | ||
metrics: List<Metric>, | ||
compilationMode: CompilationMode = CompilationMode.DEFAULT, | ||
startupMode: StartupMode? = null, | ||
@IntRange(from = 1) | ||
iterations: Int = DEFAULT_ITERATIONS, | ||
setupBlock: MacrobenchmarkScope.() -> Unit = {}, | ||
measureBlock: MacrobenchmarkScope.() -> Unit, | ||
) { | ||
measureRepeated( | ||
packageName = packageName, | ||
metrics = metrics, | ||
compilationMode = compilationMode, | ||
startupMode = startupMode, | ||
iterations = iterations, | ||
setupBlock = setupBlock, | ||
measureBlock = measureBlock, | ||
) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without looking too deeply, I'm curious whether adding this to our normal manifest could have any impact on production performance and if so whether there is there an easy way to create a "copy" app that we could use under test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question, looking into the docs, the profileable tag makes the non debuggable builds profileable on 29+, it doesn't look like it has any impact on the performance since it's designed to benchmark release builds for more accurate perf metrics.