Merge pull request #458 from android/tm/fix-benchmarks

Fix benchmarks
pull/466/head
Tomáš Mlynarič 2 years ago committed by GitHub
commit b425f9560d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,3 +1,21 @@
/*
* Copyright 2022 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
*
* https://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.
*/
import com.google.samples.apps.nowinandroid.FlavorDimension
import com.google.samples.apps.nowinandroid.NiaFlavor
/*
* Copyright 2022 The Android Open Source Project
*
@ -21,10 +39,12 @@ plugins {
android {
defaultConfig {
applicationId = "com.google.samples.apps.niacatalog"
versionCode = 1
versionName = "0.0.1" // X.Y.Z; X = Major, Y = minor, Z = Patch level
// The UI catalog does not depend on content from the app, however, it depends on modules
// which do, so we must specify a default value for the contentType dimension.
missingDimensionStrategy("contentType", "demo")
missingDimensionStrategy(FlavorDimension.contentType.name, NiaFlavor.demo.name)
}
packagingOptions {
@ -33,6 +53,15 @@ android {
}
}
namespace = "com.google.samples.apps.niacatalog"
buildTypes {
val release by getting {
// To publish on the Play store a private signing key is required, but to allow anyone
// who clones the code to sign and run the release variant, use the debug signing key.
// TODO: Abstract the signing configuration to a separate file to avoid hardcoding this.
signingConfig = signingConfigs.getByName("debug")
}
}
}
dependencies {
@ -41,4 +70,4 @@ dependencies {
implementation(libs.androidx.activity.compose)
implementation(libs.accompanist.flowlayout)
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2021 The Android Open Source Project
* Copyright 2022 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.
@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.google.samples.apps.nowinandroid.NiaBuildType
plugins {
id("nowinandroid.android.application")
@ -38,10 +39,11 @@ android {
buildTypes {
val debug by getting {
applicationIdSuffix = ".debug"
applicationIdSuffix = NiaBuildType.DEBUG.applicationIdSuffix
}
val release by getting {
isMinifyEnabled = true
applicationIdSuffix = NiaBuildType.RELEASE.applicationIdSuffix
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
// To publish on the Play store a private signing key is required, but to allow anyone
@ -57,9 +59,8 @@ android {
signingConfig = signingConfigs.getByName("debug")
// Only use benchmark proguard rules
proguardFiles("benchmark-rules.pro")
// FIXME enabling minification breaks access to demo backend.
isMinifyEnabled = false
applicationIdSuffix = ".benchmark"
isMinifyEnabled = true
applicationIdSuffix = NiaBuildType.BENCHMARK.applicationIdSuffix
}
}

File diff suppressed because it is too large Load Diff

@ -14,6 +14,7 @@
* limitations under the License.
*/
import com.android.build.api.dsl.ManagedVirtualDevice
import com.google.samples.apps.nowinandroid.NiaBuildType
import com.google.samples.apps.nowinandroid.configureFlavors
plugins {
@ -26,6 +27,8 @@ android {
defaultConfig {
minSdk = 23
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
buildConfigField("String", "APP_BUILD_TYPE_SUFFIX", "\"\"")
}
buildFeatures {
@ -41,13 +44,24 @@ android {
isDebuggable = true
signingConfig = signingConfigs.getByName("debug")
matchingFallbacks.add("release")
buildConfigField(
"String",
"APP_BUILD_TYPE_SUFFIX",
"\"${NiaBuildType.BENCHMARK.applicationIdSuffix ?: ""}\""
)
}
}
// Use the same flavor dimensions as the application to allow generating Baseline Profiles on prod,
// which is more close to what will be shipped to users (no fake data), but has ability to run the
// benchmarks on demo, so we benchmark on stable data.
configureFlavors(this)
configureFlavors(this) { flavor ->
buildConfigField(
"String",
"APP_FLAVOR_SUFFIX",
"\"${flavor.applicationIdSuffix ?: ""}\""
)
}
targetProjectPath = ":app"
experimentalProperties["android.experimental.self-instrumenting"] = true
@ -73,7 +87,6 @@ dependencies {
implementation(libs.androidx.test.rules)
implementation(libs.androidx.test.uiautomator)
implementation(libs.androidx.benchmark.macro)
implementation(libs.androidx.profileinstaller)
}
androidComponents {

@ -0,0 +1,48 @@
/*
* Copyright 2022 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
*
* https://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.test.uiautomator
import androidx.test.uiautomator.HasChildrenOp.AT_LEAST
import androidx.test.uiautomator.HasChildrenOp.AT_MOST
import androidx.test.uiautomator.HasChildrenOp.EXACTLY
// These helpers need to be in the androidx.test.uiautomator package,
// because the abstract class has package local method that needs to be implemented.
/**
* Condition will be satisfied if given element has specified count of children
*/
fun untilHasChildren(
childCount: Int = 1,
op: HasChildrenOp = AT_LEAST
): UiObject2Condition<Boolean> {
return object : UiObject2Condition<Boolean>() {
override fun apply(element: UiObject2): Boolean {
return when (op) {
AT_LEAST -> element.childCount >= childCount
EXACTLY -> element.childCount == childCount
AT_MOST -> element.childCount <= childCount
}
}
}
}
enum class HasChildrenOp {
AT_LEAST,
EXACTLY,
AT_MOST
}

@ -16,16 +16,25 @@
package com.google.samples.apps.nowinandroid
import androidx.test.uiautomator.Direction
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
import com.google.samples.apps.nowinandroid.benchmarks.BuildConfig
/**
* Convenience parameter to use proper package name with regards to build type and build flavor.
*/
val PACKAGE_NAME = StringBuilder("com.google.samples.apps.nowinandroid").apply {
if (BuildConfig.FLAVOR != "prod") {
append(".${BuildConfig.FLAVOR}")
}
if (BuildConfig.BUILD_TYPE != "release") {
append(".${BuildConfig.BUILD_TYPE}")
}
}.toString()
val PACKAGE_NAME = buildString {
append("com.google.samples.apps.nowinandroid")
append(BuildConfig.APP_FLAVOR_SUFFIX)
append(BuildConfig.APP_BUILD_TYPE_SUFFIX)
}
fun UiDevice.flingElementDownUp(element: UiObject2) {
// Set some margin from the sides to prevent triggering system navigation
element.setGestureMargin(displayWidth / 5)
element.fling(Direction.DOWN)
waitForIdle()
element.fling(Direction.UP)
}

@ -20,7 +20,6 @@ import androidx.benchmark.macro.ExperimentalBaselineProfilesApi
import androidx.benchmark.macro.junit4.BaselineProfileRule
import androidx.test.uiautomator.By
import com.google.samples.apps.nowinandroid.PACKAGE_NAME
import com.google.samples.apps.nowinandroid.bookmarks.bookmarksScrollFeedDownUp
import com.google.samples.apps.nowinandroid.foryou.forYouScrollFeedDownUp
import com.google.samples.apps.nowinandroid.foryou.forYouSelectAuthors
import com.google.samples.apps.nowinandroid.foryou.forYouWaitForContent
@ -48,14 +47,14 @@ class BaselineProfileGenerator {
// Scroll the feed critical user journey
forYouWaitForContent()
forYouSelectAuthors()
forYouSelectAuthors(true)
forYouScrollFeedDownUp()
// Navigate to saved screen
device.findObject(By.text("Saved")).click()
device.waitForIdle()
bookmarksScrollFeedDownUp()
// TODO: we need to implement adding stuff to bookmarks before able to scroll it
// bookmarksScrollFeedDownUp()
// Navigate to interests screen
device.findObject(By.text("Interests")).click()

@ -18,11 +18,9 @@ package com.google.samples.apps.nowinandroid.bookmarks
import androidx.benchmark.macro.MacrobenchmarkScope
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Direction
import com.google.samples.apps.nowinandroid.flingElementDownUp
fun MacrobenchmarkScope.bookmarksScrollFeedDownUp() {
val feedList = device.findObject(By.res("bookmarks:feed"))
feedList.fling(Direction.DOWN)
device.waitForIdle()
feedList.fling(Direction.UP)
device.flingElementDownUp(feedList)
}

@ -18,27 +18,57 @@ package com.google.samples.apps.nowinandroid.foryou
import androidx.benchmark.macro.MacrobenchmarkScope
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Direction
import androidx.test.uiautomator.Until
import androidx.test.uiautomator.untilHasChildren
import com.google.samples.apps.nowinandroid.flingElementDownUp
fun MacrobenchmarkScope.forYouWaitForContent() {
// Wait until content is loaded
device.wait(Until.hasObject(By.text("What are you interested in?")), 30_000)
// Wait until content is loaded by checking if authors are loaded
device.wait(Until.gone(By.res("forYou:loadingWheel")), 5_000)
// Sometimes, the loading wheel is gone, but the content is not loaded yet
// So we'll wait here for authors to be sure
val obj = device.findObject(By.res("forYou:authors"))
obj.wait(untilHasChildren(), 30_000)
}
fun MacrobenchmarkScope.forYouSelectAuthors() {
/**
* Selects some authors, which will show the feed content for them.
* [recheckAuthorsIfChecked] Authors may be already checked from the previous iteration.
*/
fun MacrobenchmarkScope.forYouSelectAuthors(recheckAuthorsIfChecked: Boolean = false) {
val authors = device.findObject(By.res("forYou:authors"))
// select some authors to show some feed content
// Set gesture margin from sides not to trigger system gesture navigation
val horizontalMargin = 10 * authors.visibleBounds.width() / 100
authors.setGestureMargins(horizontalMargin, 0, horizontalMargin, 0)
// Select some authors to show some feed content
repeat(3) { index ->
val author = authors.children[index % authors.childCount]
author.click()
device.waitForIdle()
when {
// Author wasn't checked, so just do that
!author.isChecked -> {
author.click()
device.waitForIdle()
}
// The author was checked already and we want to recheck it, so just do it twice
recheckAuthorsIfChecked -> {
repeat(2) {
author.click()
device.waitForIdle()
}
}
else -> {
// The author is checked, but we don't recheck it
}
}
}
}
fun MacrobenchmarkScope.forYouScrollFeedDownUp() {
val feedList = device.findObject(By.res("forYou:feed"))
feedList.fling(Direction.DOWN)
device.waitForIdle()
feedList.fling(Direction.UP)
device.flingElementDownUp(feedList)
}

@ -18,21 +18,17 @@ package com.google.samples.apps.nowinandroid.interests
import androidx.benchmark.macro.MacrobenchmarkScope
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Direction
import androidx.test.uiautomator.Until
import com.google.samples.apps.nowinandroid.flingElementDownUp
fun MacrobenchmarkScope.interestsScrollTopicsDownUp() {
val topicsList = device.findObject(By.res("interests:topics"))
topicsList.fling(Direction.DOWN)
device.waitForIdle()
topicsList.fling(Direction.UP)
device.flingElementDownUp(topicsList)
}
fun MacrobenchmarkScope.interestsScrollPeopleDownUp() {
val peopleList = device.findObject(By.res("interests:people"))
peopleList.fling(Direction.DOWN)
device.waitForIdle()
peopleList.fling(Direction.UP)
device.flingElementDownUp(peopleList)
}
fun MacrobenchmarkScope.interestsWaitForTopics() {

@ -0,0 +1,27 @@
/*
* Copyright 2022 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
*
* https://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 com.google.samples.apps.nowinandroid
/**
* This is shared between :app and :benchmarks module to provide configurations type safety.
*/
@Suppress("unused")
enum class NiaBuildType(val applicationIdSuffix: String? = null) {
DEBUG(".debug"),
RELEASE,
BENCHMARK(".benchmark")
}

@ -2,10 +2,11 @@ package com.google.samples.apps.nowinandroid
import com.android.build.api.dsl.ApplicationExtension
import com.android.build.api.dsl.ApplicationProductFlavor
import com.android.build.api.dsl.ApplicationVariantDimension
import com.android.build.api.dsl.CommonExtension
import com.android.build.api.dsl.ProductFlavor
import org.gradle.api.Project
@Suppress("EnumEntryName")
enum class FlavorDimension {
contentType
}
@ -13,20 +14,23 @@ enum class FlavorDimension {
// The content for the app can either come from local static data which is useful for demo
// purposes, or from a production backend server which supplies up-to-date, real content.
// These two product flavors reflect this behaviour.
enum class Flavor (val dimension : FlavorDimension, val applicationIdSuffix : String? = null) {
@Suppress("EnumEntryName")
enum class NiaFlavor(val dimension: FlavorDimension, val applicationIdSuffix: String? = null) {
demo(FlavorDimension.contentType),
prod(FlavorDimension.contentType, ".prod")
}
fun Project.configureFlavors(
commonExtension: CommonExtension<*, *, *, *>
commonExtension: CommonExtension<*, *, *, *>,
flavorConfigurationBlock: ProductFlavor.(flavor: NiaFlavor) -> Unit = {}
) {
commonExtension.apply {
flavorDimensions += FlavorDimension.contentType.name
productFlavors {
Flavor.values().forEach{
NiaFlavor.values().forEach {
create(it.name) {
dimension = it.dimension.name
flavorConfigurationBlock(this, it)
if (this@apply is ApplicationExtension && this is ApplicationProductFlavor) {
if (it.applicationIdSuffix != null) {
this.applicationIdSuffix = it.applicationIdSuffix
@ -36,4 +40,4 @@ fun Project.configureFlavors(
}
}
}
}
}

@ -126,15 +126,14 @@ internal fun ForYouScreen(
onNewsResourcesCheckedChanged: (String, Boolean) -> Unit,
modifier: Modifier = Modifier,
) {
val isOnboardingLoading = onboardingUiState is OnboardingUiState.Loading
val isFeedLoading = feedState is NewsFeedUiState.Loading
// Workaround to call Activity.reportFullyDrawn from Jetpack Compose.
// This code should be called when the UI is ready for use
// and relates to Time To Full Display.
val onboardingLoaded =
onboardingUiState !is OnboardingUiState.Loading
val feedLoaded = feedState !is NewsFeedUiState.Loading
if (onboardingLoaded && feedLoaded) {
// TODO replace with ReportDrawnWhen { } once androidx.activity-compose 1.7.0 is used (currently alpha)
if (!isSyncing && !isOnboardingLoading && !isFeedLoading) {
val localView = LocalView.current
// We use Unit to call reportFullyDrawn only on the first recomposition,
// however it will be called again if this composable goes out of scope.
@ -196,9 +195,7 @@ internal fun ForYouScreen(
}
}
AnimatedVisibility(
visible = isSyncing ||
feedState is NewsFeedUiState.Loading ||
onboardingUiState is OnboardingUiState.Loading,
visible = isSyncing || isFeedLoading || isOnboardingLoading,
enter = slideInVertically(
initialOffsetY = { fullHeight -> -fullHeight },
) + fadeIn(),
@ -211,7 +208,9 @@ internal fun ForYouScreen(
modifier = Modifier.fillMaxWidth()
) {
NiaOverlayLoadingWheel(
modifier = Modifier.align(Alignment.Center),
modifier = Modifier
.align(Alignment.Center)
.testTag("forYou:loadingWheel"),
contentDesc = loadingContentDescription
)
}

Loading…
Cancel
Save