The Simplest Guide To Generate Baseline Profiles Continuously

How to empower builds with GitHub Actions

Victor Oliveira
Level Up Coding

--

This article intends to talk about Macrobenchmark, Baseline Profiles, and GitHub Actions. Basic knowledge of these subjects is required ahead. Bear in mind that lessons learned here related to GitHub Actions can be ported to any other type of project.

Icons by Ruslan Babkin

Over the past years, Google has been working on many updates regarding Performance improvement. Baseline Profiles is one of the latest tools released by the company and they claim it can improve Android apps startup time by up to 40%. If you ever tried Baseline Profiles, you’re probably aware that generating rules, even with the Macrobenchmark library, might be very painful. This article provides an easy way to automatically generate these rules, no matter the size of your project as long it’s versioned on GitHub.

Whether choosing to start a new project or not, yours should set compileSdk 32 or higher and support Android Gradle Plugin 7.3.0 or higher.

Note: Gradle Kotlin DSL of the following scripts is available here.

/**
*
* :app Gradle file
*
**/
android {
compileSdk 33
}

/**
*
* Top-level Gradle file
*
**/
plugins {
id 'com.android.application' version '7.3.0' apply false
id 'com.android.library' version '7.3.0' apply false
id 'com.android.test' version '7.3.0' apply false
}

#1(Optional) Create an empty Android project

#2 — Add Macrobenchmark library to your project

Macrobenchmark library. Module addition User Interface.

#3 — Create a benchmark-rules.pro file, under the app directory, to make sure Proguard won't obfuscate the benchmark build variant and set it as the Proguard file on thebenchmark build type

# Benchmark-rules.pro
# Disables obfuscation for benchmark builds.
-dontobfuscate
/**
*
* :app Gradle file
*
**/
android {
buildTypes {
benchmark {
signingConfig signingConfigs.debug
proguardFiles("benchmark-rules.pro")
matchingFallbacks = ['release']
debuggable false
}
}
}

#4 — Add at least one Managed Device


import com.android.build.api.dsl.ManagedVirtualDevice

/**
*
* :benchmark Gradle file
*
**/
android {
testOptions {
managedDevices {
devices {
pixel2Api31(ManagedVirtualDevice) {
device = "Pixel 2"
apiLevel = 31
systemImageSource = "aosp"
}
}
}
}
}

#5 — Add at least one @Test to collect Baseline Profiles

/**
*
* :benchmark module
*
**/

package com.example.benchmark

import androidx.benchmark.macro.ExperimentalBaselineProfilesApi
import androidx.benchmark.macro.junit4.BaselineProfileRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

/**
*
* ExperimentalBaselineProfilesApi is required so that
* macrobenchmark tests can be skipped when running
* the :benchmark:pixel2Api31BenchmarkAndroidTest task with
* androidx.benchmark.enabledRules=BaselineProfile
* as an argument. Check out actions.yml file.
*
**/
@OptIn(ExperimentalBaselineProfilesApi::class)
class BaselineProfileGenerator {

@get:Rule
val baselineProfileRule = BaselineProfileRule()

/**
*
* @packageName Same as your applicationId (:app Gradle file)
*
**/
@Test
fun startup() =
baselineProfileRule.collectBaselineProfile(
packageName = "com.example.myapplication",
profileBlock = {
startActivityAndWait()
}
)
}

#6 — Add the GitHub Actions script

# Workflow name
name: baseline-profiles

# Workflow title
run-name: ${{ github.actor }} requested a workflow

# Event trigger so this actions gets executed every time a push is made on the main branch
# Change this event to what suits your project best.
# Read more at https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows
on:
push:
branches:
- main

# Environment variables (Optional)
# Small projects might have signingConfigs locally. This could lead to failures on GitHub Actions.
# If that's the case, upload your properties defined locally to GitHub Secrets.

# On your signingConfigs, you can recover GitHub Secrets using: variable = System.getenv("VARIABLE")

# Then uncomment this block properly defining your uploaded variables
# env:
# VARIABLE: ${{ secrets.VARIABLE }}

# Read more at https://docs.github.com/en/actions/security-guides/encrypted-secrets

# Jobs to executed on GitHub machines
jobs:

# Job name
generate-baseline-profiles:

# Operating system where the job gets to be executed
runs-on: macos-latest

# Job steps
steps:

# Checks your code out on the machine
- uses: actions/checkout@v3

# Sets java up
- uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 11

# Sets gradle up
- name: Setup Gradle
uses: gradle/gradle-build-action@v2

# Grants execute permission to gradle (safety step)
- name: Grant Permissions to gradlew
run: chmod +x gradlew

# Cleans managed device if previously settle and space currently is not available
- name: Clean Managed Devices
run: ./gradlew cleanManagedDevices --unused-only

# Generates Baseline Profile
- name: Generate Baseline Profile

## swiftshader_indirect:
## Required to use software acceleration on machines that can't use hardware acceleration

## enabledRules=BaselineProfile:
## Required to skip macrobenchmark tests.
## Read more at https://developer.android.com/topic/performance/benchmarking/benchmarking-in-ci#real-devices
## Read more at https://developer.android.com/topic/performance/benchmarking/macrobenchmark-overview#configuration-errors

## gradle.workers.max:
## Required to avoid running multiple managed device tests concurrently. Read more at https://issuetracker.google.com/issues/193118030

run: ./gradlew :benchmark:pixel2Api31BenchmarkAndroidTest -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect" -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile -Dorg.gradle.workers.max=4

# Sets the Baseline Profile on its proper place so it gets correctly bundled into Play Store
- name: Move & Rename Baseline Profiles
run: |
mv -f benchmark/build/outputs/managed_device_android_test_additional_output/pixel2Api31/BaselineProfileGenerator_startup-baseline-prof.txt app/src/main/baseline-prof.txt

# Commits the generated Baseline Profile to your origin/remote
- name: Commit Baseline Profiles
run: |
git config --global user.name 'Baseline Profiles - GitHub Actions'
git config --global user.email 'github@actions'
git add app/src/main/baseline-prof.txt
git commit -m "Generate baseline profiles"
git push

#7 — Merge all changes into the main branch

EOF — In about ten minutes, a successful job should be present on the Actions tab and, finally, the last commit in your repository will present changes of a recently added baseline-prof.txt file.

Baseline Profile rules generated by GitHub Actions committed on the main branch

GitHub Actions is a very powerful tool to generate Baseline Profile rules in a scalable way. By properly invoking other actions/* and replacing run commands on the *.yml file, many other workflows could be created. GitHub Actions is one of the best ways to automate workflows that suits your project best.

Now, tell me, how do you think Baseline Profiles or GitHub Actions could come in handy? Tell me more about your perspectives in the comments. Thank you for clapping & sharing if you enjoyed it, and don’t forget to follow me to stay tuned for upcoming articles! =}

--

--