1 /* <lambda>null2 * Copyright 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.benchmark.macro 18 19 import android.annotation.SuppressLint 20 import android.os.Build 21 import android.util.Log 22 import androidx.annotation.RequiresApi 23 import androidx.annotation.RestrictTo 24 import androidx.benchmark.Arguments 25 import androidx.benchmark.Shell 26 import androidx.profileinstaller.ProfileInstallReceiver 27 import androidx.profileinstaller.ProfileInstaller 28 29 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // temporary, revert 30 object ProfileInstallBroadcast { 31 private val receiverName = ProfileInstallReceiver::class.java.name 32 33 /** 34 * Returns null on success, error string on suppress-able error, or throws if profileinstaller 35 * not up to date. 36 * 37 * Returned error strings aren't thrown, to let the calling function decide strictness. 38 */ 39 fun installProfile(packageName: String): String? { 40 Log.d(TAG, "Profile Installer - Install profile") 41 // For baseline profiles, we trigger this broadcast to force the baseline profile to be 42 // installed synchronously 43 val action = ProfileInstallReceiver.ACTION_INSTALL_PROFILE 44 // Use an explicit broadcast given the app was force-stopped. 45 when (val result = Shell.amBroadcast("-a $action $packageName/$receiverName")) { 46 null, 47 // 0 is returned by the platform by default, and also if no broadcast receiver 48 // receives the broadcast. 49 0 -> { 50 return "The baseline profile install broadcast was not received. " + 51 "This most likely means that the profileinstaller library is missing " + 52 "from the target apk." 53 } 54 ProfileInstaller.RESULT_INSTALL_SUCCESS -> { 55 return null // success! 56 } 57 ProfileInstaller.RESULT_ALREADY_INSTALLED -> { 58 throw RuntimeException( 59 "Unable to install baseline profile. This most likely means that the " + 60 "latest version of the profileinstaller library is not being used. " + 61 "Please use the latest profileinstaller library version " + 62 "in the target app." 63 ) 64 } 65 ProfileInstaller.RESULT_UNSUPPORTED_ART_VERSION -> { 66 val sdkInt = Build.VERSION.SDK_INT 67 throw RuntimeException( 68 if (sdkInt <= 23) { 69 "Baseline profiles aren't supported on this device version," + 70 " as all apps are fully ahead-of-time compiled." 71 } else { 72 "The device SDK version ($sdkInt) isn't supported" + 73 " by the target app's copy of profileinstaller." + 74 if (sdkInt in 31..33) { 75 " Please use profileinstaller `1.2.1`" + 76 " or newer for API 31-33 support" 77 } else if (sdkInt >= 34) { 78 " Please use profileinstaller `1.4.0`" + 79 " or newer for API 34+ support" 80 } else { 81 "" 82 } 83 } 84 ) 85 } 86 ProfileInstaller.RESULT_BASELINE_PROFILE_NOT_FOUND -> { 87 return "No baseline profile was found in the target apk." 88 } 89 ProfileInstaller.RESULT_NOT_WRITABLE, 90 ProfileInstaller.RESULT_DESIRED_FORMAT_UNSUPPORTED, 91 ProfileInstaller.RESULT_IO_EXCEPTION, 92 ProfileInstaller.RESULT_PARSE_EXCEPTION -> { 93 throw RuntimeException("Baseline Profile wasn't successfully installed") 94 } 95 else -> { 96 throw RuntimeException("unrecognized ProfileInstaller result code: $result") 97 } 98 } 99 } 100 101 private fun nullResultErrorMessage(broadcastLabel: String, versionAdded: String): String = 102 "The $broadcastLabel broadcast was not received. " + 103 "This most likely means that the `androidx.profileinstaller` library is " + 104 "missing from the target app, or is too old. Please use `$versionAdded` or " + 105 "newer. For more information refer to the release notes at " + 106 "https://developer.android.com/jetpack/androidx/releases/profileinstaller." 107 108 /** 109 * Uses skip files for avoiding interference from ProfileInstaller when using 110 * [CompilationMode.None]. 111 * 112 * Operation name is one of `WRITE_SKIP_FILE` or `DELETE_SKIP_FILE`. 113 * 114 * Returned error strings aren't thrown, to let the calling function decide strictness. 115 */ 116 fun skipFileOperation( 117 packageName: String, 118 @Suppress("SameParameterValue") operation: String 119 ): String? { 120 Log.d(TAG, "Profile Installer - Skip File Operation: $operation") 121 // Redefining constants here, because these are only defined in the latest alpha for 122 // ProfileInstaller. 123 // Use an explicit broadcast given the app was force-stopped. 124 val action = "androidx.profileinstaller.action.SKIP_FILE" 125 val operationKey = "EXTRA_SKIP_FILE_OPERATION" 126 val extras = "$operationKey $operation" 127 val result = Shell.amBroadcast("-a $action -e $extras $packageName/$receiverName") 128 return when { 129 result == null || result == 0 -> { 130 // 0 is returned by the platform by default, and also if no broadcast receiver 131 // receives the broadcast. 132 nullResultErrorMessage( 133 broadcastLabel = "baseline profile skip file", 134 versionAdded = "1.2.0-alpha03", 135 ) 136 } 137 operation == "WRITE_SKIP_FILE" && result == 10 -> { // RESULT_INSTALL_SKIP_FILE_SUCCESS 138 null // success! 139 } 140 operation == "DELETE_SKIP_FILE" && result == 11 -> { // RESULT_DELETE_SKIP_FILE_SUCCESS 141 null // success! 142 } 143 else -> { 144 throw RuntimeException("unrecognized ProfileInstaller result code: $result") 145 } 146 } 147 } 148 149 /** 150 * Save any in-memory profile data in the target app to disk, so it can be used for compilation. 151 * 152 * Returned error strings aren't thrown, to let the calling function decide strictness. 153 */ 154 @RequiresApi(24) 155 fun saveProfile(packageName: String): String? { 156 Log.d(TAG, "Profile Installer - Save Profile") 157 val action = "androidx.profileinstaller.action.SAVE_PROFILE" 158 return when (val result = Shell.amBroadcast("-a $action $packageName/$receiverName")) { 159 null, 160 0 -> { 161 // 0 is returned by the platform by default, and also if no broadcast receiver 162 // receives the broadcast. This can be because the package name specified is 163 // incorrect or an old version of profile installer was used. 164 nullResultErrorMessage( 165 broadcastLabel = "save profile", 166 versionAdded = "1.3.1", 167 ) 168 } 169 12 -> { // RESULT_SAVE_PROFILE_SIGNALLED 170 // While this is observed to be fast for simple/sample apps, 171 // this can take up significantly longer on large apps 172 // especially on low end devices (see b/316082056) 173 @Suppress("BanThreadSleep") Thread.sleep(1000) 174 null // success! 175 } 176 else -> { 177 // We don't bother supporting RESULT_SAVE_PROFILE_SKIPPED here, 178 // since we already perform SDK_INT checks and use @RequiresApi(24) 179 throw RuntimeException("unrecognized ProfileInstaller result code: $result") 180 } 181 } 182 } 183 184 enum class Operation(val extraValue: String, val minimumVersion: String, val successCode: Int) { 185 DropShaderCache( 186 extraValue = "DROP_SHADER_CACHE", 187 minimumVersion = "1.3.0-alpha02", 188 successCode = ProfileInstaller.RESULT_BENCHMARK_OPERATION_SUCCESS 189 ), 190 SaveProfile( 191 extraValue = "SAVE_PROFILE", 192 minimumVersion = "1.5.0-alpha01", 193 successCode = ProfileInstaller.RESULT_SAVE_PROFILE_SIGNALLED 194 ), 195 } 196 197 private fun benchmarkOperation( 198 packageName: String, 199 operation: Operation, 200 pid: Int = -1 201 ): String? { 202 Log.d(TAG, "Profile Installer - Benchmark Operation: ${operation.extraValue}") 203 // Redefining constants here, because these are only defined in the latest alpha for 204 // ProfileInstaller. 205 // Use an explicit broadcast given the app was force-stopped. 206 val action = "androidx.profileinstaller.action.BENCHMARK_OPERATION" 207 val operationKey = "EXTRA_BENCHMARK_OPERATION" 208 val pidExtra = 209 if (pid != -1) { 210 require(operation == Operation.SaveProfile) 211 " --ei EXTRA_PID $pid" 212 } else { 213 require(operation != Operation.SaveProfile) 214 "" 215 } 216 val broadcastArguments = 217 "-a $action" + 218 " -e $operationKey ${operation.extraValue}" + 219 pidExtra + 220 " $packageName/$receiverName" 221 return when (val result = Shell.amBroadcast(broadcastArguments)) { 222 null, 223 0, 224 16 -> { // BENCHMARK_OPERATION_UNKNOWN 225 // 0 is returned by the platform by default, and also if no broadcast receiver 226 // receives the broadcast. 227 228 // NOTE: may need to update this over time for different versions, 229 // based on operation string 230 "The ${operation.extraValue} broadcast was not received. " + 231 "This most likely means that the `androidx.profileinstaller` library " + 232 "used by the target apk is old. Please use `${operation.minimumVersion}`" + 233 " or newer. For more information refer to the release notes at " + 234 "https://developer.android.com/jetpack/androidx/releases/profileinstaller. " + 235 "If you are already using 'androidx.profileinstaller' library and still seeing " + 236 "error, verify: 1) androidx.profileinstaller.ProfileInstallReceiver appears " + 237 "unobfuscated in your APK's AndroidManifest and dex, and 2) the following " + 238 "command executes successfully (should print ${operation.successCode}}): " + 239 "adb shell am broadcast $broadcastArguments" 240 } 241 15 -> { // RESULT_BENCHMARK_OPERATION_FAILURE 242 "The $operation broadcast failed." 243 } 244 else -> { 245 if (result == operation.successCode) { 246 null // success! 247 } else { 248 throw RuntimeException("unrecognized ProfileInstaller result code: $result") 249 } 250 } 251 } 252 } 253 254 fun dropShaderCache(packageName: String): String? = 255 benchmarkOperation(packageName, Operation.DropShaderCache) 256 257 data class SaveProfileResult(val processCount: Int, val error: String?) { 258 init { 259 require(error == null || processCount > 0) { 260 "Error only valid if processes are found running," + 261 " error = $error, processCount = $processCount" 262 } 263 } 264 } 265 266 @SuppressLint("BanThreadSleep") 267 @RequiresApi(24) 268 fun saveProfilesForAllProcesses(packageName: String): SaveProfileResult { 269 val processes = Shell.getRunningPidsAndProcessesForPackage(packageName) 270 processes 271 .sortedBy { it.processName } 272 .forEach { runningProcess -> 273 Log.d(TAG, "Saving profiles for process $runningProcess") 274 if (runningProcess.processName.contains(":")) { 275 // Only attempt the new broadcast on processes that require it - 276 // processes that aren't the main registered process 277 // this lets single process apps run with profileinstaller 1.3/1.4 278 // without needing the save profile operation. 279 // NOTE: we are assuming here that the target doesn't move 280 // ProfileInstallReceiver to a different process for simplicity 281 val error = 282 benchmarkOperation( 283 packageName = packageName, 284 operation = Operation.SaveProfile, 285 pid = runningProcess.pid 286 ) 287 if (error != null) return SaveProfileResult(processes.size, error) 288 Thread.sleep(Arguments.saveProfileWaitMillis) 289 } else { 290 val error = saveProfile(packageName) 291 if (error != null) return SaveProfileResult(processes.size, error) 292 } 293 } 294 return SaveProfileResult(processes.size, null) // success! 295 } 296 } 297