From a6d5d21e0ba58bc95fbf1ac44f1707588911ccf4 Mon Sep 17 00:00:00 2001 From: Sebastian Kaspari Date: Wed, 24 Nov 2021 18:52:02 +0100 Subject: [PATCH] Issue #22580: Track APK size in perfherder --- app/build.gradle | 10 ++ buildSrc/build.gradle | 4 + buildSrc/src/main/java/Dependencies.kt | 3 + .../mozilla/fenix/gradle/tasks/ApkSizeTask.kt | 143 ++++++++++++++++++ taskcluster/ci/build/kind.yml | 4 + .../fenix_taskgraph/transforms/build.py | 14 ++ 6 files changed, 178 insertions(+) create mode 100644 buildSrc/src/main/java/org/mozilla/fenix/gradle/tasks/ApkSizeTask.kt diff --git a/app/build.gradle b/app/build.gradle index bf1b1ef68..21b4716c0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,3 +1,5 @@ +import org.mozilla.fenix.gradle.tasks.ApkSizeTask + plugins { id "com.jetbrains.python.envs" version "0.0.26" id "com.google.protobuf" version "0.8.17" @@ -801,3 +803,11 @@ ext.updateExtensionVersion = { task, extDir -> expand(values) } } + +android.applicationVariants.all { variant -> + tasks.register("apkSize${variant.name.capitalize()}", ApkSizeTask) { + variantName = variant.name + apks = variant.outputs.collect { output -> output.outputFile.name } + dependsOn "package${variant.name.capitalize()}" + } +} diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index d2cf0f015..99fb3a898 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -17,3 +17,7 @@ repositories { mavenCentral() } } + +dependencies { + implementation "org.json:json:20210307" +} diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index d39d0b11d..7fe957802 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -2,6 +2,9 @@ * 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/. */ +// If you ever need to force a toolchain rebuild (taskcluster) then edit the following comment. +// FORCE REBUILD 2021-11-24 + object Versions { const val kotlin = "1.5.31" const val coroutines = "1.5.2" diff --git a/buildSrc/src/main/java/org/mozilla/fenix/gradle/tasks/ApkSizeTask.kt b/buildSrc/src/main/java/org/mozilla/fenix/gradle/tasks/ApkSizeTask.kt new file mode 100644 index 000000000..409bdf62f --- /dev/null +++ b/buildSrc/src/main/java/org/mozilla/fenix/gradle/tasks/ApkSizeTask.kt @@ -0,0 +1,143 @@ +/* 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.gradle.tasks + +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.TaskAction +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import java.io.File +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Paths + +/** + * Gradle task for determining the size of APKs and logging them in a perfherder compatible format. + */ +open class ApkSizeTask : DefaultTask() { + /** + * Name of the build variant getting built. + */ + @Input + var variantName: String? = null + + /** + * List of APKs that get build for the build variant. + */ + @Input + var apks: List? = null + + @TaskAction + fun logApkSize() { + val apkSizes = determineApkSizes() + if (apkSizes.isEmpty()) { + println("Couldn't determine APK sizes for perfherder") + return + } + + val json = buildPerfherderJson(apkSizes) ?: return + + println("PERFHERDER_DATA: $json") + } + + private fun determineApkSizes(): Map { + val basePath = listOf( + "${project.projectDir}", "build", "outputs", "apk", variantName + ).joinToString(File.separator) + + return requireNotNull(apks).associateWith { apk -> + val rawPath = "$basePath${File.separator}$apk" + + try { + val path = Paths.get(rawPath) + Files.size(path) + } catch (t: Throwable) { + println("Could not determine size of $apk ($rawPath)") + t.printStackTrace() + 0 + } + }.filter { (_, size) -> size > 0 } + } + + /** + * Returns perfherder compatible JSON for tracking the file size of APKs. + * + * ``` + * { + * "framework": { + * "name": "build_metrics" + * }, + * "suites": [ + * { + * "name": "apk-size-[debug,nightly,beta,release]", + * "lowerIsBetter": true, + * "subtests": [ + * { "name": "app-arm64-v8a-debug.apk", "value": 98855735 }, + * { "name": "app-armeabi-v7a-debug.apk", "value": 92300031 }, + * { "name": "app-x86-debug.apk", "value": 103410909 }, + * { "name": "app-x86_64-debug.apk", "value": 102465675 } + * ], + * "value":98855735, + * "shouldAlert":false + * } + * ] + * } + * ``` + */ + private fun buildPerfherderJson(apkSize: Map): JSONObject? { + return try { + val data = JSONObject() + + val framework = JSONObject() + framework.put("name", "build_metrics") + data.put("framework", framework) + + val suites = JSONArray() + + val suite = JSONObject() + suite.put("name", "apk-size-$variantName") + suite.put("value", getSummarySize(apkSize)) + suite.put("lowerIsBetter", true) + suite.put("shouldAlert", false) + + val subtests = JSONArray() + apkSize.forEach { (apk, size) -> + val subtest = JSONObject() + subtest.put("name", apk) + subtest.put("value", size) + subtests.put(subtest) + } + suite.put("subtests", subtests) + + suites.put(suite) + + data.put("suites", suites) + + data + } catch (e: JSONException) { + println("Couldn't generate perfherder JSON") + e.printStackTrace() + null + } + } +} + +/** + * Returns a summarized size for the APKs. This is the main value that is getting tracked. The size + * of the individual APKs will be reported as "subtests". + */ +private fun getSummarySize(apkSize: Map): Long { + val arm64size = apkSize.keys.find { it.contains("arm64") }?.let { apk -> apkSize[apk] } + if (arm64size != null) { + // If available we will report the size of the arm64 APK as the summary. This is the most + // important and most installed APK. + return arm64size + } + + // If there's no arm64 APK then we calculate a simple average. + return apkSize.values.sum() / apkSize.size +} diff --git a/taskcluster/ci/build/kind.yml b/taskcluster/ci/build/kind.yml index 6e7ed7d47..e7114e080 100644 --- a/taskcluster/ci/build/kind.yml +++ b/taskcluster/ci/build/kind.yml @@ -51,6 +51,7 @@ jobs: run-on-tasks-for: [github-pull-request, github-push] run: gradle-build-type: debug + track-apk-size: true treeherder: symbol: debug(B) @@ -157,6 +158,7 @@ jobs: include-shippable-secrets: true run: gradle-build-type: nightly + track-apk-size: true run-on-tasks-for: [] treeherder: symbol: nightly(B) @@ -169,6 +171,7 @@ jobs: filter-incomplete-translations: true run: gradle-build-type: beta + track-apk-size: true treeherder: symbol: beta(B) @@ -195,6 +198,7 @@ jobs: filter-incomplete-translations: true run: gradle-build-type: release + track-apk-size: true treeherder: symbol: release(B) diff --git a/taskcluster/fenix_taskgraph/transforms/build.py b/taskcluster/fenix_taskgraph/transforms/build.py index f0d2ba155..8ac7c1c0c 100644 --- a/taskcluster/fenix_taskgraph/transforms/build.py +++ b/taskcluster/fenix_taskgraph/transforms/build.py @@ -74,6 +74,20 @@ def build_gradle_command(config, tasks): yield task +@transforms.add +def track_apk_size(config, tasks): + for task in tasks: + gradle_build_type = task["run"]["gradle-build-type"] + variant_config = get_variant(gradle_build_type) + + should_track_apk_size = task["run"].pop("track-apk-size", False) + if should_track_apk_size: + task["run"]["gradlew"].append( + "apkSize{}".format(variant_config["name"].capitalize()) + ) + + yield task + @transforms.add def extra_gradle_options(config, tasks): for task in tasks: