tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ApkSizePlugin.kt (5211B)


      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/. */
      4 
      5 import org.gradle.api.DefaultTask
      6 import org.gradle.api.Plugin
      7 import org.gradle.api.Project
      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
     13 import java.io.File
     14 import java.nio.file.Files
     15 import java.nio.file.Paths
     16 
     17 class ApkSizePlugin : Plugin<Project> {
     18    override fun apply(project: Project) = Unit
     19 }
     20 
     21 /**
     22 * Gradle task for determining the size of APKs and logging them in a perfherder compatible format.
     23 */
     24 open class ApkSizeTask : DefaultTask() {
     25    /**
     26     * Name of the build variant getting built.
     27     */
     28    @Input
     29    var variantName: String? = null
     30 
     31    /**
     32     * List of APKs that get build for the build variant.
     33     */
     34    @Input
     35    var apks: List<String>? = null
     36 
     37    private val buildDir = project.layout.buildDirectory.get().asFile
     38 
     39    @TaskAction
     40    fun logApkSize() {
     41        val apkSizes = determineApkSizes()
     42        if (apkSizes.isEmpty()) {
     43            println("Couldn't determine APK sizes for perfherder")
     44            return
     45        }
     46 
     47        val json = buildPerfherderJson(apkSizes) ?: return
     48        val isAutomation = System.getenv("MOZ_AUTOMATION") == "1"
     49        val uploadPath = System.getenv("MOZ_PERFHERDER_UPLOAD")
     50        if (isAutomation && uploadPath != null) {
     51            println("PERFHERDER_DATA: $json")
     52            val outputFile = File(uploadPath)
     53            outputFile.parentFile?.mkdirs()
     54            outputFile.writeText(json.toString())
     55        }
     56    }
     57 
     58    private fun determineApkSizes(): Map<String, Long> {
     59        val variantOutputPath = variantName?.removePrefix("fenix")?.lowercase()
     60        val basePath = listOf(
     61            "$buildDir", "outputs", "apk", "fenix", variantOutputPath
     62        ).joinToString(File.separator)
     63 
     64        return requireNotNull(apks).associateWith { apk ->
     65            val rawPath = "$basePath${File.separator}$apk"
     66 
     67            try {
     68                val path = Paths.get(rawPath)
     69                Files.size(path)
     70            } catch (t: Throwable) {
     71                println("Could not determine size of $apk ($rawPath)")
     72                t.printStackTrace()
     73                0
     74            }
     75        }.filter { (_, size) -> size > 0 }
     76    }
     77 
     78    /**
     79     * Returns perfherder compatible JSON for tracking the file size of APKs.
     80     *
     81     * ```
     82     * {
     83     *   "framework": {
     84     *     "name": "build_metrics"
     85     *   },
     86     *   "suites": [
     87     *     {
     88     *       "name": "apk-size-[debug,nightly,beta,release]",
     89     *       "lowerIsBetter": true,
     90     *       "subtests": [
     91     *         { "name": "app-arm64-v8a-debug.apk", "value": 98855735 },
     92     *         { "name": "app-armeabi-v7a-debug.apk", "value": 92300031 },
     93     *         { "name": "app-x86-debug.apk", "value": 103410909 },
     94     *         { "name": "app-x86_64-debug.apk", "value": 102465675 }
     95     *       ],
     96     *       "value":98855735,
     97     *       "shouldAlert":false
     98     *     }
     99     *   ]
    100     * }
    101     * ```
    102     */
    103    private fun buildPerfherderJson(apkSize: Map<String, Long>): JSONObject? {
    104        return try {
    105            val data = JSONObject()
    106 
    107            val framework = JSONObject()
    108            framework.put("name", "build_metrics")
    109            data.put("framework", framework)
    110 
    111            val suites = JSONArray()
    112 
    113            val suite = JSONObject()
    114            suite.put("name", "apk-size-$variantName")
    115            suite.put("value", getSummarySize(apkSize))
    116            suite.put("lowerIsBetter", true)
    117            suite.put("alertChangeType", "absolute")
    118            suite.put("alertThreshold", 1024 * 1024)
    119 
    120            // Debug variants do not have alerts
    121            if (variantName?.contains("debug", ignoreCase = true) == true) {
    122                suite.put("shouldAlert", false)
    123            }
    124 
    125            val subtests = JSONArray()
    126            apkSize.forEach { (apk, size) ->
    127                val subtest = JSONObject()
    128                subtest.put("name", apk)
    129                subtest.put("value", size)
    130                subtests.put(subtest)
    131            }
    132            suite.put("subtests", subtests)
    133 
    134            suites.put(suite)
    135 
    136            data.put("suites", suites)
    137 
    138            data
    139        } catch (e: JSONException) {
    140            println("Couldn't generate perfherder JSON")
    141            e.printStackTrace()
    142            null
    143        }
    144    }
    145 }
    146 
    147 /**
    148 * Returns a summarized size for the APKs. This is the main value that is getting tracked. The size
    149 * of the individual APKs will be reported as "subtests".
    150 */
    151 private fun getSummarySize(apkSize: Map<String, Long>): Long {
    152    val arm64size = apkSize.keys.find { it.contains("arm64") }?.let { apk -> apkSize[apk] }
    153    if (arm64size != null) {
    154        // If available we will report the size of the arm64 APK as the summary. This is the most
    155        // important and most installed APK.
    156        return arm64size
    157    }
    158 
    159    // If there's no arm64 APK then we calculate a simple average.
    160    return apkSize.values.sum() / apkSize.size
    161 }