diff --git a/.gitattributes b/.gitattributes index f1c588b..5a0bbd7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,13 @@ *.apk filter=lfs diff=lfs merge=lfs -text + +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + +# Binary files should be left untouched +*.jar binary diff --git a/.github/workflows/PerformanceCheck.yaml b/.github/workflows/PerformanceCheck.yaml new file mode 100644 index 0000000..c9239a0 --- /dev/null +++ b/.github/workflows/PerformanceCheck.yaml @@ -0,0 +1,53 @@ +name: Performance Check + +on: + pull_request: + types: [opened, synchronize, reopened] + workflow_dispatch: + +jobs: + benchmark-android: + uses: Frozen-Bytes/workflow-testing/.github/workflows/android-macrobenchmark-gradle.yml@main + strategy: + fail-fast: false + matrix: + include: + - name: "Pixel" + profile: "pixel" + name: ${{ matrix.name }} + with: + api-level: 34 + target: google_apis + arch: x86_64 + profile: "${{ matrix.profile }}" + gradle-cache-readonly: false + baseline-branch: "main" + candidate-branch: "${{ github.head_ref }}" + runs: 5 + args: "--verbose --all-methods --measures TID FFO MEM_RSS_MAX --aggregate median --fit 5" + instrument-args: "-e package com.google.samples.apps.nowinandroid.Generator" + artifact-name: "benchcomp-${{ matrix.name }}.json" + artifact-macrobenchmark-name: "${{ matrix.name }}-raw-macrobenchmark" + device-alias: "${{ matrix.name }}" + + publish-report: + runs-on: ubuntu-latest + needs: benchmark-android + permissions: + pull-requests: write + contents: read + + steps: + - name: Create Bot Token + id: app-token + uses: actions/create-github-app-token@v3 + with: + client-id: ${{ vars.GH_APP_CLIENT_ID }} + private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} + + - name: Publish Report + uses: Frozen-Bytes/actions/publish-report@v3 + with: + github-token: ${{ steps.app-token.outputs.token }} + artifact-prefix: "benchcomp-" + comment-on-pr: ${{ github.event_name == 'pull_request' && 'true' || 'false' }} diff --git a/.github/workflows/android-macrobenchmark-gradle.yml b/.github/workflows/android-macrobenchmark-gradle.yml new file mode 100644 index 0000000..cf26dc9 --- /dev/null +++ b/.github/workflows/android-macrobenchmark-gradle.yml @@ -0,0 +1,248 @@ +name: Android Macrobenchmark CI (Gradle) + +on: + workflow_call: + inputs: + api-level: + description: 'API level of the platform and system image - e.g. 23, 33, 35-ext15, Baklava' + required: true + type: string + system-image-api-level: + description: 'API level of the system image - e.g. 34-ext10, 35-ext15. If not set the `api-level` input will be used.' + required: false + type: string + target: + description: 'target of the system image - e.g. default, google_apis, google_apis_ps16k, google_apis_playstore, google_apis_playstore_16k, aosp_atd, google_atd, android-wear, android-wear-cn, android-tv, google-tv, android-automotive, android-automotive-playstore or android-desktop' + default: 'default' + type: string + arch: + description: 'CPU architecture of the system image - x86, x86_64 or arm64-v8a' + default: 'x86' + type: string + profile: + description: 'hardware profile used for creating the AVD - e.g. `Nexus 6`' + type: string + cores: + description: 'the number of cores to use for the emulator' + default: 2 + type: string + ram-size: + description: 'size of RAM to use for this AVD, in KB or MB, denoted with K or M. - e.g. `2048M`' + type: string + heap-size: + description: 'size of heap to use for this AVD in MB. - e.g. `512M`' + type: string + sdcard-path-or-size: + description: 'path to the SD card image for this AVD or the size of a new SD card image to create for this AVD, in KB or MB, denoted with K or M. - e.g. `path/to/sdcard`, or `1000M`' + type: string + disk-size: + description: 'disk size to use for this AVD. Either in bytes or KB, MB or GB, when denoted with K, M or G' + type: string + avd-name: + description: 'custom AVD name used for creating the Android Virtual Device' + default: 'test' + type: string + force-avd-creation: + description: 'whether to force create the AVD by overwriting an existing AVD with the same name as `avd-name` - `true` or `false`' + default: 'true' + type: string + emulator-boot-timeout: + description: 'Emulator boot timeout in seconds. If it takes longer to boot, the action would fail - e.g. `300` for 5 minutes' + default: '600' + type: string + emulator-port: + description: 'Port to run emulator on, allows to run multiple emulators on the same physical machine' + default: '5554' + type: string + emulator-options: + description: 'command-line options used when launching the emulator - e.g. `-no-window -no-snapshot -camera-back emulated`' + default: '-no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim' + type: string + disable-animations: + description: 'whether to disable animations - true or false' + default: 'true' + type: string + disable-spellchecker: + description: 'whether to disable the Android spell checker framework, a common source of flakiness in text fields - `true` or `false`' + default: 'false' + type: string + disable-linux-hw-accel: + description: 'whether to disable hardware acceleration on Linux machines - `true` or `false` or `auto`' + default: 'auto' + type: string + enable-hw-keyboard: + description: 'whether to enable hardware keyboard - `true` or `false`.' + default: 'false' + type: string + emulator-build: + description: 'build number of a specific version of the emulator binary to use - e.g. `6061023` for emulator v29.3.0.0' + type: string + working-directory: + description: 'A custom working directory - e.g. `./android` if your root Gradle project is under the `./android` sub-directory within your repository' + type: string + ndk: + description: 'version of NDK to install - e.g. 21.0.6113669' + type: string + cmake: + description: 'version of CMake to install - e.g. 3.10.2.4988404' + type: string + channel: + description: 'Channel to download the SDK components from - `stable`, `beta`, `dev`, `canary`' + default: 'stable' + type: string + pre-emulator-launch-script: + description: 'custom script to run after creating the AVD and before launching the emulator - e.g. `./adjust-emulator-configs.sh`' + type: string + + # Build Options + baseline-branch: + type: string + default: main + candidate-branch: + type: string + default: "" + gradle-cache-readonly: + description: | + When 'true', existing entries will be read from the cache but no entries will be written. + By default this value is 'false' for workflows on the GitHub default branch and 'true' for workflows on other branches. + required: false + default: ${{ github.event.repository != null && github.ref_name != github.event.repository.default_branch }} + type: boolean + + # Benchmark.sh Options + runs: + description: 'Number of benchmark iterations (passed to --runs)' + default: '1' + type: string + retry: + description: 'Number of times to retry the benchmark on failure' + default: '3' + type: string + instrument-args: + description: 'Extra arguments passed to instrumentation runner' + default: '' + type: string + + # Benchcomp Options + args: + description: 'Arguments passed directly to benchcomp' + default: '--verbose -A -a' + type: string + device-alias: + description: 'Attach an alias to the AVD device identifier' + default: '' + type: string + + # Artifacts + artifact-name: + description: 'Name of the uploaded benchcomp result artifact' + default: 'benchcomp.json' + type: string + artifact-macrobenchmark-name: + description: 'Name of the uploaded raw MacroBenchmark results' + default: 'raw-macrobenchmarks' + type: string + upload-macrobenchmark: + description: 'Whether to upload raw MacroBenchmark results' + default: 'true' + type: string + +jobs: + benchmark-android: + runs-on: ubuntu-latest + + steps: + - name: Free Disk Space + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf "/usr/local/share/boost" + + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Checkout branch (Baseline) + uses: actions/checkout@v4 + with: + ref: ${{ inputs.baseline-branch }} + path: baseline + + - name: Checkout branch (Candidate) + uses: actions/checkout@v4 + with: + ref: ${{ (inputs.candidate-branch != '' && inputs.candidate-branch) || (github.head_ref != '' && github.head_ref) || github.ref_name }} + path: candidate + + - name: Setup JDK 17 + uses: actions/setup-java@v4 + with: + distribution: "zulu" + java-version: "17" + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + # By default, The setup-gradle action will only write to the cache from Jobs on the default (main/master) branch. + # Jobs on other branches will read entries from the cache but will not write updated entries. + cache-read-only: ${{ inputs.gradle-cache-readonly }} + + - name: Build APKs (Candidate) + working-directory: ./candidate + run: | + chmod +x ./gradlew + ./gradlew :benchmarks:assembleDebug + + - name: List Dirs (Candidate) + working-directory: ./candidate + run: ls -alR + + - name: Android Macrobenchmark CI + uses: Frozen-Bytes/actions/android-macrobenchmark-ci@v3 + with: + # Emulator Options + api-level: ${{ inputs.api-level }} + system-image-api-level: ${{ inputs.system-image-api-level }} + target: ${{ inputs.target }} + arch: ${{ inputs.arch }} + profile: ${{ inputs.profile }} + cores: ${{ inputs.cores }} + ram-size: ${{ inputs.ram-size }} + heap-size: ${{ inputs.heap-size }} + sdcard-path-or-size: ${{ inputs.sdcard-path-or-size }} + disk-size: ${{ inputs.disk-size }} + avd-name: ${{ inputs.avd-name }} + force-avd-creation: ${{ inputs.force-avd-creation }} + emulator-boot-timeout: ${{ inputs.emulator-boot-timeout }} + emulator-port: ${{ inputs.emulator-port }} + emulator-options: ${{ inputs.emulator-options }} + disable-animations: ${{ inputs.disable-animations }} + disable-spellchecker: ${{ inputs.disable-spellchecker }} + disable-linux-hw-accel: ${{ inputs.disable-linux-hw-accel }} + enable-hw-keyboard: ${{ inputs.enable-hw-keyboard }} + emulator-build: ${{ inputs.emulator-build }} + working-directory: ${{ inputs.working-directory }} + ndk: ${{ inputs.ndk }} + cmake: ${{ inputs.cmake }} + channel: ${{ inputs.channel }} + pre-emulator-launch-script: ${{ inputs.pre-emulator-launch-script }} + + # Benchmark.sh Inputs (Required) + baseline-apk: "./baseline/apks/org.wikipedia_r_50595.apk" + candidate-apk: "./candidate/apks/org.wikipedia_r_50595.apk" + benchmark-apk: "./candidate/benchmarks/build/outputs/apk/debug/benchmarks-debug.apk" + # Benchmark.sh Options + runs: ${{ inputs.runs }} + retry: ${{ inputs.retry }} + instrument-args: ${{ inputs.instrument-args }} + + # Benchcomp Options + args: ${{ inputs.args }} + device-alias: "${{ inputs.device-alias }}" + + # Artifacts + artifact-name: "${{ inputs.artifact-name }}" + artifact-macrobenchmark-name: "${{ inputs.artifact-macrobenchmark-name }}" + upload-macrobenchmark: ${{ inputs.upload-macrobenchmark }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba5b4cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,58 @@ +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build + +# Ignore Kotlin plugin data +.kotlin + +# built application files +*.apk +*.ap_ + +# files for the dex VM +*.dex + +# Java class files +*.class + +# generated files +bin/ +gen/ +out/ +build/ +generated/ + +# Local configuration file (sdk path, etc) +local.properties + +# Eclipse project files +.classpath +.project + +# Windows thumbnail db +.DS_Store + +# IDEA/Android Studio project files, because +# the project can be imported from settings.gradle.kts +*.iml +.idea/* +!.idea/copyright +# Keep the code styles. +!/.idea/codeStyles +/.idea/codeStyles/* +!/.idea/codeStyles/Project.xml +!/.idea/codeStyles/codeStyleConfig.xml + +# Gradle cache +.gradle + +# Sandbox stuff +_sandbox + +# Android Studio captures folder +captures/ + +# Kotlin +.kotlin diff --git a/benchmarks/build.gradle.kts b/benchmarks/build.gradle.kts new file mode 100644 index 0000000..3fbd5a7 --- /dev/null +++ b/benchmarks/build.gradle.kts @@ -0,0 +1,59 @@ +/* + * 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. + */ + + plugins { + // This tells Gradle this is a standalone test module and enables 'android {}' + alias(libs.plugins.android.test) + alias(libs.plugins.kotlin.android) + } + + android { + namespace = "com.google.samples.apps.nowinandroid.benchmarks" + compileSdk = 34 + + defaultConfig { + minSdk = 28 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + + buildConfigField("String", "APP_BUILD_TYPE_SUFFIX", "\"\"") + } + + buildFeatures { + buildConfig = true + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = "17" + } + + targetProjectPath = ":" + experimentalProperties["android.experimental.self-instrumenting"] = true +} + +dependencies { + implementation(libs.androidx.benchmark.macro) + implementation(libs.androidx.test.core) + implementation(libs.androidx.test.espresso.core) + implementation(libs.androidx.test.ext) + implementation(libs.androidx.test.rules) + implementation(libs.androidx.test.runner) + implementation(libs.androidx.test.uiautomator) +} diff --git a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/Generator/OpenArticle.kt b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/Generator/OpenArticle.kt new file mode 100644 index 0000000..9942548 --- /dev/null +++ b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/Generator/OpenArticle.kt @@ -0,0 +1,198 @@ +package com.google.samples.apps.nowinandroid.Generator + +import androidx.benchmark.macro.BaselineProfileMode +import androidx.benchmark.macro.CompilationMode +import androidx.benchmark.macro.ExperimentalMetricApi +import androidx.benchmark.macro.FrameTimingMetric +import androidx.benchmark.macro.MemoryUsageMetric +import androidx.benchmark.macro.StartupMode +import androidx.benchmark.macro.StartupTimingMetric +import androidx.benchmark.macro.junit4.MacrobenchmarkRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalMetricApi::class) +@RunWith(AndroidJUnit4::class) +class GeneratedOpenArticle { + @get:Rule + val benchmarkRule = MacrobenchmarkRule() + + @Test + fun OpenArticle() = benchmarkRule.measureRepeated( + packageName = "org.wikipedia", + metrics = listOf(MemoryUsageMetric(MemoryUsageMetric.Mode.Max), FrameTimingMetric()), + startupMode = StartupMode.WARM, + compilationMode = CompilationMode.Partial( + baselineProfileMode = BaselineProfileMode.Disable, + warmupIterations = 3, + ), + iterations = 10, + setupBlock = { + pressHome() + startActivityAndWait() + device.waitForIdle() + device.wait(Until.hasObject(By.pkg("org.wikipedia").depth(0)), 5000L) + }, + ) { + /* + explain: I need to open the Wikipedia app to begin the task of searching for and opening an article. + summary: The task is to open Wikipedia, search for an article, and then open it. The first step is to launch the Wikipedia application. + */ + // ACTION: AWAKE + + /* + explain: I need to click the forward arrow to move past this welcome screen and access the app's main features for searching articles. + summary: The task is to open Wikipedia, search for an article, and then open it. I have successfully opened the app and am now navigating through its initial welcome screen. + */ + device.click(((938.0f / 1000.0f) * device.displayWidth).toInt(), ((950.0f / 1000.0f) * device.displayHeight).toInt()) + device.waitForIdle() + Thread.sleep(1000L) + + /* + explain: I need to click the forward arrow at the bottom right to continue through the app's initial setup screens and reach the main interface for searching articles. + summary: The task is to open Wikipedia, search for an article, and then open it. I have successfully opened the app but am currently stuck on a 'Data & Privacy' screen that requires navigation through initial setup steps. + */ + device.click(((938.0f / 1000.0f) * device.displayWidth).toInt(), ((951.0f / 1000.0f) * device.displayHeight).toInt()) + device.waitForIdle() + Thread.sleep(1000L) + + /* + explain: I am clicking the 'next' arrow in the bottom right corner because it is the only way forward from this setup screen and my previous attempt did not register. + summary: The task is to open Wikipedia, search for an article, and then open it. I have successfully opened the app but am currently stuck on a language selection screen that requires navigation through initial setup steps. + */ + device.click(((938.0f / 1000.0f) * device.displayWidth).toInt(), ((951.0f / 1000.0f) * device.displayHeight).toInt()) + device.waitForIdle() + Thread.sleep(1000L) + + /* + explain: I need to click the 'Skip' button to bypass this initial setup screen and access the app's main functionality for searching articles. + summary: The task is to open Wikipedia, search for an article, and then open it. I have successfully opened the app but am currently stuck on a language selection screen that requires navigation through initial setup steps. + */ + device.click(((938.0f / 1000.0f) * device.displayWidth).toInt(), ((951.0f / 1000.0f) * device.displayHeight).toInt()) + device.waitForIdle() + Thread.sleep(1000L) + + /* + explain: I need to get past this setup screen to access the search functionality. Clicking 'Skip' is the quickest way to bypass these initial steps and proceed with my task. + summary: The task is to open Wikipedia, search for an article, and then open it. I have successfully opened the app but am currently stuck on a language selection screen that requires navigation through initial setup steps. + */ + device.click(((70.0f / 1000.0f) * device.displayWidth).toInt(), ((952.0f / 1000.0f) * device.displayHeight).toInt()) + device.waitForIdle() + Thread.sleep(1000L) + + /* + explain: I need to click the 'Search' button in the bottom navigation bar to access the search functionality and find an article. + summary: The task is to open Wikipedia, search for an article, and then open it. I have successfully opened the app and am now on its main page. The next step is to initiate a search. + */ + device.click(((500.0f / 1000.0f) * device.displayWidth).toInt(), ((927.0f / 1000.0f) * device.displayHeight).toInt()) + device.waitForIdle() + Thread.sleep(1000L) + + /* + explain: I need to close this pop-up window to access the main search interface and continue with the article search. + summary: The task is to open Wikipedia, search for an article, and then open it. I have successfully opened the app but am now blocked by a pop-up dialog that needs to be dismissed before proceeding. + */ + device.click(((926.0f / 1000.0f) * device.displayWidth).toInt(), ((587.0f / 1000.0f) * device.displayHeight).toInt()) + device.waitForIdle() + Thread.sleep(1000L) + + /* + explain: I need to type a search query into the search bar to find an article and continue with the task. + summary: The task is to open Wikipedia, search for an article, and then open it. I have successfully opened the app and am now on the search page. The next step is to perform a search. + */ + device.executeShellCommand("input text Apple") + device.waitForIdle() + Thread.sleep(1000L) + + /* + explain: I need to enter a search term into the search bar to find an article. + summary: The task is to open Wikipedia, search for an article, and then open it. I have successfully opened the app and am now on the search page. The next step is to perform a search by typing in the search bar. + */ + device.executeShellCommand("input text Apple") + device.waitForIdle() + Thread.sleep(1000L) + + /* + explain: I need to enter a search term into the search bar to find an article. + summary: The task is to open Wikipedia, search for an article, and then open it. I have successfully opened the app and am on the search page. The next step is to perform a search by typing in the search bar. + */ + device.executeShellCommand("input text Apple") + device.waitForIdle() + Thread.sleep(1000L) + + /* + explain: I need to enter a search term into the search bar to find an article. Since no specific topic was given, I will use 'Apple' as a sample query. + summary: The task is to open Wikipedia, search for an article, and then open it. I have successfully opened the app and am on the search page. The next step is to perform a search by typing in the search bar. + */ + device.executeShellCommand("input text Apple") + device.waitForIdle() + Thread.sleep(1000L) + + /* + explain: I need to type a search query into the search bar to find an article and continue with the task. + summary: The task is to open Wikipedia, search for an article, and then open it. I have successfully opened the app and am on the search page. The next step is to perform a search by typing in the search bar. + */ + device.executeShellCommand("input text Artificial%sIntelligence") + device.waitForIdle() + Thread.sleep(1000L) + + /* + explain: I need to click the search bar to activate it before I can type in a search term. + summary: The task is to open Wikipedia, search for an article, and then open it. I have successfully opened the app and am on the main screen. The next step is to initiate a search by clicking the search bar. + */ + device.click(((498.0f / 1000.0f) * device.displayWidth).toInt(), ((147.0f / 1000.0f) * device.displayHeight).toInt()) + device.waitForIdle() + Thread.sleep(1000L) + + /* + explain: I need to ask you which article you want to search for, as the task instructions don't specify a particular topic. + summary: The task is to open Wikipedia and find an article. The app has been opened successfully, and the search interface is now active. The next step is to determine what article to search for. + */ + // ACTION: INFO + + /* + explain: I need to enter the article title provided by the user, 'Artificial Intelligence', into the search bar to find the relevant article. + summary: The task is to open Wikipedia and find an article. The app has been opened successfully, and I am now typing the article title 'Artificial Intelligence' into the search bar. + */ + device.executeShellCommand("input text Artificial%sIntelligence") + device.waitForIdle() + Thread.sleep(1000L) + + /* + explain: I need to complete the article title in the search bar so that I can select it from the results and open the correct article. + summary: The task is to find an article on Wikipedia. The app is open, but my previous attempt to type 'Artificial Intelligence' was incomplete. I am now completing the search term. + */ + device.executeShellCommand("input text intelligence") + device.waitForIdle() + Thread.sleep(1000L) + + /* + explain: I need to select an article from the search results to open its page and complete the task. + summary: The task is to find and open a Wikipedia article. I have successfully searched for 'Artificialintelligence' and am now selecting the first result from the list. + */ + device.click(((238.0f / 1000.0f) * device.displayWidth).toInt(), ((150.0f / 1000.0f) * device.displayHeight).toInt()) + device.waitForIdle() + Thread.sleep(1000L) + + /* + explain: I need to close the promotional pop-up that's blocking the article so I can read its contents. + summary: The task is to find and open a Wikipedia article. I have successfully opened the 'Artificial intelligence' article, but a promotional pop-up has appeared. The next step is to close this pop-up. + */ + device.click(((843.0f / 1000.0f) * device.displayWidth).toInt(), ((342.0f / 1000.0f) * device.displayHeight).toInt()) + device.waitForIdle() + Thread.sleep(1000L) + + /* + explain: I need to click the 'Got it' button to dismiss the toolbar customization prompt so I can continue reading the article. + summary: The task is to find and open a Wikipedia article. I have successfully opened the 'Artificial intelligence' article, but a promotional pop-up has appeared. The next step is to close this pop-up. + */ + device.click(((625.0f / 1000.0f) * device.displayWidth).toInt(), ((197.0f / 1000.0f) * device.displayHeight).toInt()) + device.waitForIdle() + Thread.sleep(1000L) + + } +} diff --git a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/Generator/OpenArticle.mp4 b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/Generator/OpenArticle.mp4 new file mode 100644 index 0000000..1dcef7f Binary files /dev/null and b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/Generator/OpenArticle.mp4 differ diff --git a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/Generator/StartupBenchmark.kt b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/Generator/StartupBenchmark.kt new file mode 100644 index 0000000..98369f8 --- /dev/null +++ b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/Generator/StartupBenchmark.kt @@ -0,0 +1,39 @@ +package com.google.samples.apps.nowinandroid.Generator + +import androidx.benchmark.macro.BaselineProfileMode +import androidx.benchmark.macro.CompilationMode +import androidx.benchmark.macro.ExperimentalMetricApi +import androidx.benchmark.macro.FrameTimingMetric +import androidx.benchmark.macro.MemoryUsageMetric +import androidx.benchmark.macro.StartupMode +import androidx.benchmark.macro.StartupTimingMetric +import androidx.benchmark.macro.junit4.MacrobenchmarkRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class GeneratedStartupBenchmark { + @get:Rule + val benchmarkRule = MacrobenchmarkRule() + + @Test + fun startupTime() = benchmarkRule.measureRepeated( + packageName = "org.wikipedia", + metrics = listOf(StartupTimingMetric()), + startupMode = StartupMode.COLD, + compilationMode = CompilationMode.Partial( + baselineProfileMode = BaselineProfileMode.Disable, + warmupIterations = 1, + ), + iterations = 10, + setupBlock = { pressHome() }, + ) { + startActivityAndWait() + device.waitForIdle() + device.wait(Until.hasObject(By.pkg("org.wikipedia").depth(0)), 5000L) + } +} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..9c73d44 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,11 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This is a general purpose Gradle build. + * Learn more about Gradle by exploring our Samples at https://docs.gradle.org/9.6.1/samples + */ + + plugins { + alias(libs.plugins.android.test) apply false + alias(libs.plugins.kotlin.android) apply false + } diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..7af8a11 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,67 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Ensure important default jvmargs aren't overwritten. See https://github.com/gradle/gradle/issues/19750 +# +# For more information about how Gradle memory options were chosen: +# - Metaspace See https://www.jasonpearson.dev/metaspace-in-jvm-builds/ +# - SoftRefLRUPolicyMSPerMB would default to 1000 which with a 4gb heap translates to ~51 minutes. +# A value of 1 means ~4 seconds before SoftRefs can be collected, which means its realistic to +# collect them as needed during a build that should take seconds to minutes. +# - CodeCache normally defaults to a very small size. Increasing it from platform defaults of 32-48m +# because of how many classes can be loaded into memory and then cached as native compiled code +# for a small speed boost. +org.gradle.jvmargs=-Dfile.encoding=UTF-8 -XX:+UseG1GC -XX:SoftRefLRUPolicyMSPerMB=1 -XX:ReservedCodeCacheSize=256m -XX:+HeapDumpOnOutOfMemoryError -Xmx4g -Xms4g + +# For more information about how Kotlin Daemon memory options were chosen: +# - Kotlin JVM args only inherit Xmx, ReservedCodeCache, and MaxMetaspace. Since we are specifying +# other args we need to specify all of them here. +# - We're using the Kotlin Gradle Plugin's default value for ReservedCodeCacheSize, if we do not then +# the Gradle JVM arg value for ReservedCodeCacheSize will be used. +kotlin.daemon.jvmargs=-Dfile.encoding=UTF-8 -XX:+UseG1GC -XX:SoftRefLRUPolicyMSPerMB=1 -XX:ReservedCodeCacheSize=320m -XX:+HeapDumpOnOutOfMemoryError -Xmx4g -Xms4g + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +org.gradle.parallel=true + +# Not encouraged by Gradle and can produce weird results. Wait for isolated projects instead. +org.gradle.configureondemand=false + +# Enable caching between builds. +org.gradle.caching=true + +# Enable configuration caching between builds. +org.gradle.configuration-cache=true +org.gradle.configuration-cache.parallel=true +# This option is set because of https://github.com/google/play-services-plugins/issues/246 +# to generate the Configuration Cache regardless of incompatible tasks. +# See https://github.com/android/nowinandroid/issues/1022 before using it. +org.gradle.configuration-cache.problems=warn + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official + +# Disable build features that are enabled by default, +# https://developer.android.com/build/releases/gradle-plugin#default-changes +android.defaults.buildfeatures.resvalues=false +android.defaults.buildfeatures.shaders=false + +# Run Roborazzi screenshot tests with the local tests +roborazzi.test.verify=true + +# Prevent uninstall app after instrumented tests +# https://issuetracker.google.com/issues/295039976 +android.injected.androidTest.leaveApksInstalledAfterRun=true + +ksp.project.isolation.enabled=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..dff3ea0 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,25 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/version_catalogs.html#sec::toml-dependencies-format +[versions] +androidxBenchmarkMacro = "1.3.0" # Update to latest stable versions +androidxTestCore = "1.6.1" +androidxTestEspresso = "3.6.1" +androidxTestExt = "1.2.1" +androidxTestRules = "1.6.1" +androidxTestRunner = "1.6.1" +androidxUiAutomator = "2.3.0" +androidGradlePlugin = "8.4.0" +kotlinAndroid = "1.9.24" + +[libraries] +androidx-benchmark-macro = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "androidxBenchmarkMacro" } +androidx-test-core = { group = "androidx.test", name = "core", version.ref = "androidxTestCore" } +androidx-test-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidxTestEspresso" } +androidx-test-ext = { group = "androidx.test.ext", name = "junit", version.ref = "androidxTestExt" } +androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "androidxTestRules" } +androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidxTestRunner" } +androidx-test-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "androidxUiAutomator" } + +[plugins] +android-test = { id = "com.android.test", version.ref = "androidGradlePlugin" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlinAndroid" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..b1b8ef5 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a9db115 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,9 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..379a658 --- /dev/null +++ b/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# gradlew start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh gradlew +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob//platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..a51ec4f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,82 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem gradlew startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..8532c66 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,24 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/9.6.1/userguide/multi_project_builds.html in the Gradle documentation. + */ + + pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + } + dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } + } + +rootProject.name = "wikipedia-tests" +include(":benchmarks")