1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 import org.gradle.api.DefaultTask
6 import org.gradle.api.Plugin
7 import org.gradle.api.initialization.Settings
8 import org.gradle.api.tasks.Input
9 import org.gradle.api.tasks.TaskAction
10 import org.json.JSONArray
11 import org.json.JSONException
12 import org.json.JSONObject
14 import java.nio.file.Files
15 import java.nio.file.Paths
17 class ApkSizePlugin : Plugin<Settings> {
18 override fun apply(settings: Settings) = Unit
22 * Gradle task for determining the size of APKs and logging them in a perfherder compatible format.
24 open class ApkSizeTask : DefaultTask() {
26 * Name of the build variant getting built.
29 var variantName: String? = null
32 * List of APKs that get build for the build variant.
35 var apks: List<String>? = null
39 val apkSizes = determineApkSizes()
40 if (apkSizes.isEmpty()) {
41 println("Couldn't determine APK sizes for perfherder")
45 val json = buildPerfherderJson(apkSizes) ?: return
47 println("PERFHERDER_DATA: $json")
50 private fun determineApkSizes(): Map<String, Long> {
51 val variantOutputPath = variantName?.removePrefix("fenix")?.toLowerCase()
52 val basePath = listOf(
53 "${project.projectDir}", "build", "outputs", "apk", "fenix", variantOutputPath
54 ).joinToString(File.separator)
56 return requireNotNull(apks).associateWith { apk ->
57 val rawPath = "$basePath${File.separator}$apk"
60 val path = Paths.get(rawPath)
62 } catch (t: Throwable) {
63 println("Could not determine size of $apk ($rawPath)")
67 }.filter { (_, size) -> size > 0 }
71 * Returns perfherder compatible JSON for tracking the file size of APKs.
76 * "name": "build_metrics"
80 * "name": "apk-size-[debug,nightly,beta,release]",
81 * "lowerIsBetter": true,
83 * { "name": "app-arm64-v8a-debug.apk", "value": 98855735 },
84 * { "name": "app-armeabi-v7a-debug.apk", "value": 92300031 },
85 * { "name": "app-x86-debug.apk", "value": 103410909 },
86 * { "name": "app-x86_64-debug.apk", "value": 102465675 }
95 private fun buildPerfherderJson(apkSize: Map<String, Long>): JSONObject? {
97 val data = JSONObject()
99 val framework = JSONObject()
100 framework.put("name", "build_metrics")
101 data.put("framework", framework)
103 val suites = JSONArray()
105 val suite = JSONObject()
106 suite.put("name", "apk-size-$variantName")
107 suite.put("value", getSummarySize(apkSize))
108 suite.put("lowerIsBetter", true)
109 suite.put("alertChangeType", "absolute")
110 suite.put("alertThreshold", 100 * 1024)
112 val subtests = JSONArray()
113 apkSize.forEach { (apk, size) ->
114 val subtest = JSONObject()
115 subtest.put("name", apk)
116 subtest.put("value", size)
117 subtests.put(subtest)
119 suite.put("subtests", subtests)
123 data.put("suites", suites)
126 } catch (e: JSONException) {
127 println("Couldn't generate perfherder JSON")
135 * Returns a summarized size for the APKs. This is the main value that is getting tracked. The size
136 * of the individual APKs will be reported as "subtests".
138 private fun getSummarySize(apkSize: Map<String, Long>): Long {
139 val arm64size = apkSize.keys.find { it.contains("arm64") }?.let { apk -> apkSize[apk] }
140 if (arm64size != null) {
141 // If available we will report the size of the arm64 APK as the summary. This is the most
142 // important and most installed APK.
146 // If there's no arm64 APK then we calculate a simple average.
147 return apkSize.values.sum() / apkSize.size