Skip to content
This repository has been archived by the owner on Jun 17, 2024. It is now read-only.

Bug 1807324 - Add macrobenchmarks for startup metrics #842

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions fenix/.buildconfig.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,17 @@ variants:
fileName: app-fenix-x86_64-beta-unsigned.apk
build_type: beta
name: fenixBeta
- apks:
- abi: arm64-v8a
fileName: app-fenix-arm64-v8a-benchmark-unsigned.apk
- abi: armeabi-v7a
fileName: app-fenix-armeabi-v7a-benchmark-unsigned.apk
- abi: x86
fileName: app-fenix-x86-benchmark-unsigned.apk
- abi: x86_64
fileName: app-fenix-x86_64-benchmark-unsigned.apk
build_type: benchmark
name: fenixBenchmark
- apks:
- abi: noarch
fileName: app-debug-androidTest.apk
Expand Down
10 changes: 8 additions & 2 deletions fenix/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ apply plugin: 'kotlin-parcelize'
apply plugin: 'jacoco'
apply plugin: 'androidx.navigation.safeargs.kotlin'
apply plugin: 'com.google.android.gms.oss-licenses-plugin'
apply plugin: 'androidx.benchmark'


import com.android.build.OutputFile
import groovy.json.JsonOutput
Expand Down Expand Up @@ -148,6 +146,11 @@ android {
"deepLinkScheme": deepLinkSchemeValue
]
}
benchmark releaseTemplate >> {
initWith buildTypes.nightly
applicationIdSuffix ".fenix"
debuggable false
}
}

buildFeatures {
Expand Down Expand Up @@ -447,6 +450,7 @@ nimbus {
fenixNightly: "nightly",
fenixBeta: "beta",
fenixRelease: "release",
fenixBenchmark: "developer",
]
// This is generated by the FML and should be checked into git.
// It will be fetched by Experimenter (the Nimbus experiment website)
Expand Down Expand Up @@ -620,6 +624,8 @@ dependencies {
implementation FenixDependencies.google_play_review
implementation FenixDependencies.google_play_review_ktx

implementation FenixDependencies.androidx_profileinstaller

androidTestImplementation FenixDependencies.uiautomator
androidTestImplementation "tools.fastlane:screengrab:2.0.0"
// This Falcon version is added to maven central now required for Screengrab
Expand Down
4 changes: 4 additions & 0 deletions fenix/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
android:usesCleartextTraffic="true"
tools:ignore="UnusedAttribute">

<profileable
android:shell="true"
tools:targetApi="29" />
Comment on lines +51 to +53
Copy link
Contributor

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.

Copy link
Contributor Author

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.


<!--
We inherited this entry (${applicationId}.App) from Fennec. We need to keep this as our
main launcher to avoid launcher icons on the home screen disappearing for all our users.
Expand Down
2 changes: 1 addition & 1 deletion fenix/app/src/main/java/org/mozilla/fenix/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ enum class ReleaseChannel {
object Config {
val channel = when (BuildConfig.BUILD_TYPE) {
"debug" -> ReleaseChannel.Debug
"nightly" -> ReleaseChannel.Nightly
"nightly", "benchmark" -> ReleaseChannel.Nightly
"beta" -> ReleaseChannel.Beta
"release" -> ReleaseChannel.Release
else -> {
Expand Down
70 changes: 70 additions & 0 deletions fenix/benchmark/build.gradle
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
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 app module handles this case, along with other functionality. Since benchmark module doesn't need all the glean gradle plugin's functionalities, we added this to fix the dependency resolution conflict issue. Does that help understand better? 😅 This was an issue that took some time to figure out. Jan Erik was a big here here!

Copy link
Collaborator

Choose a reason for hiding this comment

The 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
Loading

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
Loading


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"
}
}
7 changes: 7 additions & 0 deletions fenix/benchmark/src/main/AndroidManifest.xml
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,
)
}
5 changes: 4 additions & 1 deletion fenix/buildSrc/src/main/java/FenixDependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ object FenixVersions {
const val androidx_compose = "1.3.1"
const val androidx_compose_compiler = "1.4.3"
const val androidx_appcompat = "1.3.0"
const val androidx_benchmark = "1.0.0"
const val androidx_benchmark = "1.1.1"
const val androidx_biometric = "1.1.0"
const val androidx_coordinator_layout = "1.1.0"
const val androidx_constraint_layout = "2.0.4"
const val androidx_preference = "1.1.1"
const val androidx_profileinstaller = "1.2.2"
const val androidx_legacy = "1.0.0"
const val androidx_annotation = "1.5.0"
const val androidx_lifecycle = "2.5.1"
Expand Down Expand Up @@ -84,6 +85,8 @@ object FenixDependencies {
const val androidx_compose_material = "androidx.compose.material:material:${FenixVersions.androidx_compose}"
const val androidx_annotation = "androidx.annotation:annotation:${FenixVersions.androidx_annotation}"
const val androidx_benchmark_junit4 = "androidx.benchmark:benchmark-junit4:${FenixVersions.androidx_benchmark}"
const val androidx_benchmark_macro_junit4 = "androidx.benchmark:benchmark-macro-junit4:${FenixVersions.androidx_benchmark}"
const val androidx_profileinstaller = "androidx.profileinstaller:profileinstaller:${FenixVersions.androidx_profileinstaller}"
const val androidx_biometric = "androidx.biometric:biometric:${FenixVersions.androidx_biometric}"
const val androidx_fragment = "androidx.fragment:fragment-ktx:${FenixVersions.androidx_fragment}"
const val androidx_appcompat = "androidx.appcompat:appcompat:${FenixVersions.androidx_appcompat}"
Expand Down
1 change: 1 addition & 0 deletions fenix/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ plugins {
include ':app'
include ':mozilla-detekt-rules'
include ':mozilla-lint-rules'
include ':benchmark'

// Synchronized library configuration for all modules
// This "componentsVersion" number is defined in "version.txt" and should follow
Expand Down