1 /* 2 * Copyright 2021 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 18 19 import android.app.ActivityManager 20 import android.content.Context 21 import android.content.Intent 22 import android.content.IntentFilter 23 import android.content.pm.ApplicationInfo 24 import android.content.pm.PackageManager 25 import android.os.BatteryManager 26 import android.os.Build 27 import android.util.Log 28 import android.util.Printer 29 import androidx.annotation.RequiresApi 30 import androidx.annotation.RestrictTo 31 import androidx.test.platform.app.InstrumentationRegistry 32 import java.io.File 33 34 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 35 object DeviceInfo { 36 val isEmulator = 37 Build.FINGERPRINT.startsWith("generic") || 38 Build.FINGERPRINT.startsWith("unknown") || 39 Build.FINGERPRINT.contains("emulator") || 40 Build.MODEL.contains("google_sdk") || 41 Build.MODEL.startsWith("sdk_") || 42 Build.MODEL.contains("sdk_gphone64") || 43 Build.MODEL.contains("Emulator") || 44 Build.MODEL.contains("Android SDK built for") || 45 Build.MANUFACTURER.contains("Genymotion") || 46 Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic") || 47 "google_sdk" == Build.PRODUCT 48 49 val typeLabel = if (isEmulator) "emulator" else "device" 50 51 val isEngBuild = Build.FINGERPRINT.contains(":eng/") 52 private val isUserdebugBuild = Build.FINGERPRINT.contains(":userdebug/") 53 54 val profileableEnforced = !isEngBuild && !isUserdebugBuild 55 56 val isRooted = 57 Build.FINGERPRINT.contains(":userdebug/") || 58 arrayOf( 59 "/system/app/Superuser.apk", 60 "/sbin/su", 61 "/system/bin/su", 62 "/system/xbin/su", 63 "/data/local/xbin/su", 64 "/data/local/bin/su", 65 "/system/sd/xbin/su", 66 "/system/bin/failsafe/su", 67 "/data/local/su", 68 "/su/bin/su" 69 ) <lambda>null70 .any { File(it).exists() } 71 72 /** 73 * Null if BP capture is supported on this device, error string if it's not. 74 * 75 * Can be passed to assumeTrue()/require() 76 * 77 * Lazy to allow late init, after shell connection is set up 78 */ <lambda>null79 val supportsBaselineProfileCaptureError: String? by lazy { 80 if ( 81 Build.VERSION.SDK_INT >= 33 || (Build.VERSION.SDK_INT >= 28 && Shell.isSessionRooted()) 82 ) { 83 null // profile capture works, no error 84 } else { 85 "Baseline Profile collection requires API 33+, or a rooted" + 86 " device running API 28 or higher and rooted adb session (via `adb root`)." 87 } 88 } 89 90 /** 91 * Battery percentage required to avoid low battery warning. 92 * 93 * This number is supposed to be a conservative cutoff for when low-battery-triggered power 94 * savings modes (such as disabling cores) may be enabled. It's possible that 95 * [BatteryManager.EXTRA_BATTERY_LOW] is a better source of truth for this, but we want to be 96 * conservative in case the device loses power slowly while benchmarks run. 97 */ 98 const val MINIMUM_BATTERY_PERCENT = 25 99 100 val initialBatteryPercent: Int 101 102 /** String summarizing device hardware and software, for bug reporting purposes. */ 103 val deviceSummaryString: String 104 105 /** 106 * General errors about device configuration, applicable to all types of benchmark. 107 * 108 * These errors indicate no performance tests should be performed on this device, in it's 109 * current conditions. 110 */ 111 val errors: List<ConfigurationError> 112 113 /** 114 * Tracks whether the virtual kernel files have been properly configured on this OS build. 115 * 116 * If not, only recourse is to try a different device. 117 */ 118 val misconfiguredForTracing = 119 !File("/sys/kernel/tracing/trace_marker").exists() && 120 !File("/sys/kernel/debug/tracing/trace_marker").exists() 121 getMainlineAppInfonull122 private fun getMainlineAppInfo(packageName: String): ApplicationInfo? { 123 return try { 124 InstrumentationRegistry.getInstrumentation() 125 .context 126 .packageManager 127 .getApplicationInfo(packageName, PackageManager.MATCH_APEX) 128 } catch (notFoundException: PackageManager.NameNotFoundException) { 129 null 130 } 131 } 132 133 @RequiresApi(31) queryArtMainlineVersionnull134 private fun queryArtMainlineVersion(): Long { 135 val artMainlinePackage = 136 getMainlineAppInfo("com.google.android.art") 137 ?: getMainlineAppInfo("com.android.art") 138 ?: getMainlineAppInfo("com.google.android.go.art") 139 ?: getMainlineAppInfo("com.android.go.art") 140 if (artMainlinePackage == null) { 141 Log.d( 142 BenchmarkState.TAG, 143 "No ART mainline module found on API ${Build.VERSION.SDK_INT}" 144 ) 145 return if (Build.VERSION.SDK_INT >= 34) { 146 // defer error to avoid crashing during init 147 ART_MAINLINE_VERSION_UNDETECTED_ERROR 148 } else { 149 // accept missing module if we can't be sure it would have one installed (e.g. go) 150 ART_MAINLINE_VERSION_UNDETECTED 151 } 152 } 153 // This is an EXTREMELY SILLY way to find out ART's versions, but I couldn't find a better 154 // one without reflecting into ApplicationInfo.longVersionCode (not allowed in jetpack) 155 // or shell commands (slower) 156 var versionCode = -1L 157 val printer = 158 object : Printer { 159 override fun println(x: String?) { 160 if (x == null || versionCode != -1L) return 161 // We're looking to a line like the following: 162 // `enabled=true minSdkVersion=31 targetSdkVersion=34 versionCode=340818022 163 // targetSandboxVersion=1` 164 // See 165 // https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/content/pm/ApplicationInfo.java;l=1680;drc=5f97e1c49d341d58d971abef4b30de2d58a706aa 166 val prefix = " versionCode=" 167 val offset = x.indexOf(prefix) 168 if (offset >= 0) { 169 val versionString = 170 x.substring( 171 startIndex = offset + prefix.length, 172 endIndex = x.indexOf(' ', offset + prefix.length) 173 ) 174 versionCode = versionString.toLong() 175 } 176 } 177 } 178 artMainlinePackage.dump(printer, "") 179 check(versionCode > 0) { "Unable to parse ART version code" } 180 return versionCode 181 } 182 183 val isLowRamDevice: Boolean 184 185 init { 186 val context = InstrumentationRegistry.getInstrumentation().targetContext 187 188 val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED) 189 initialBatteryPercent = <lambda>null190 context.registerReceiver(null, filter)?.run { 191 val level = 192 if (getBooleanExtra(BatteryManager.EXTRA_PRESENT, true)) { 193 getIntExtra(BatteryManager.EXTRA_LEVEL, 100) 194 } else { 195 // If the device has no battery consider it full for this check. 196 100 197 } 198 val scale = getIntExtra(BatteryManager.EXTRA_SCALE, 100) 199 level * 100 / scale 200 } ?: 100 201 202 isLowRamDevice = 203 (context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).isLowRamDevice 204 205 deviceSummaryString = 206 "DeviceInfo(Brand=${Build.BRAND}" + 207 ", Model=${Build.MODEL}" + 208 ", SDK=${Build.VERSION.SDK_INT}" + 209 ", BuildFp=${Build.FINGERPRINT})" 210 211 errors = 212 listOfNotNull( 213 conditionalError( 214 hasError = isEngBuild, 215 id = "ENG-BUILD", 216 summary = "Running on Eng Build", 217 message = 218 """ 219 Benchmark is running on device flashed with a '-eng' build. Eng builds 220 of the platform drastically reduce performance to enable testing 221 changes quickly. For this reason they should not be used for 222 benchmarking. Use a '-user' or '-userdebug' system image. 223 """ 224 .trimIndent() 225 ), 226 conditionalError( 227 hasError = isEmulator, 228 id = "EMULATOR", 229 summary = "Running on Emulator", 230 message = 231 """ 232 Benchmark is running on an emulator, which is not representative of 233 real user devices. Use a physical device to benchmark. Emulator 234 benchmark improvements might not carry over to a real user's 235 experience (or even regress real device performance). 236 """ 237 .trimIndent() 238 ), 239 conditionalError( 240 hasError = initialBatteryPercent < MINIMUM_BATTERY_PERCENT, 241 id = "LOW-BATTERY", 242 summary = "Device has low battery ($initialBatteryPercent)", 243 message = 244 """ 245 When battery is low, devices will often reduce performance (e.g. disabling big 246 cores) to save remaining battery. This occurs even when they are plugged in. 247 Wait for your battery to charge to at least $MINIMUM_BATTERY_PERCENT%. 248 Currently at $initialBatteryPercent%. 249 """ 250 .trimIndent() 251 ) 252 ) 253 } 254 255 /** 256 * Starting with the first Android U release, ART mainline drops optimizations after method 257 * tracing occurs, so we disable tracing on those mainline versions. 258 * 259 * Fix cherry picked into 341513000, so we exclude that value 260 * 261 * See b/303660864 262 */ 263 private val ART_MAINLINE_VERSIONS_AFFECTING_METHOD_TRACING = 340000000L.until(341513000) 264 265 /** 266 * Starting with an API 35 change cherry-picked to mainline, ART traces class init. 267 * 268 * Fix cherry picked into 341511000 269 * 270 * See b/292294133 271 */ 272 const val ART_MAINLINE_MIN_VERSION_CLASS_LOAD_TRACING = 341511000L 273 274 /** 275 * Starting with an API 34 change cherry-picked to mainline, when `verify`-compiled, ART will 276 * save loaded classes to disk to prevent subsequent cold starts from reinitializing after the 277 * first startup. 278 * 279 * This can only happen once, and may not occur if the app doesn't have enough time to save the 280 * classes. Additionally, the list of classes is not updated in subsequent starts - it is 281 * possible for an ineffective runtime image to be generated, e.g. from a trivial broadcast 282 * receiver wakeup (again, only if the app has enough time to save the image). Experiments on an 283 * API 35 emulator show that runtime images are generally saved roughly 4 seconds after an app 284 * starts up. 285 * 286 * To disable this behavior, we re-compile with verify after each `kill` to clear profiles when 287 * desired. 288 * 289 * See b/368404173 290 * 291 * @see androidx.benchmark.macro.MacrobenchmarkScope.KillFlushMode.ClearArtRuntimeImage 292 * @see ART_MAINLINE_MIN_VERSION_VERIFY_CLEARS_RUNTIME_IMAGE 293 */ 294 private const val ART_MAINLINE_MIN_VERSION_RUNTIME_IMAGE = 340800000L 295 296 /** 297 * Starting with an API 35 backported with mainline, an additional `verify` will clear runtime 298 * images. 299 * 300 * Without this functionality, --reset (root & pre API 34) or reinstall is needed to reset. 301 */ 302 private const val ART_MAINLINE_MIN_VERSION_VERIFY_CLEARS_RUNTIME_IMAGE = 350800000L 303 304 /** 305 * ART mainline 990090000 means the module is built from source in the system image and isn't 306 * updatable, and thus expectations should be conservative - assume that any potential bug on 307 * the current SDK version may be present on this device. 308 * 309 * Ideally, we'd have a minimum release build ID, but these may not be consistently and easily 310 * sortable. 311 */ 312 private const val ART_MAINLINE_INTERNAL_BUILD_MIN = 990000000 313 314 /** 315 * Used when mainline version failed to detect, but this is accepted due to low API level (<34) 316 * where presence isn't guaranteed (e.g. go devices) 317 */ 318 const val ART_MAINLINE_VERSION_UNDETECTED = -1L 319 320 /** 321 * Used when mainline version failed to detect, and should throw an error when running a 322 * microbenchmark 323 */ 324 const val ART_MAINLINE_VERSION_UNDETECTED_ERROR = -100L 325 326 val artMainlineVersion = 327 when { 328 Build.VERSION.SDK_INT >= 31 -> queryArtMainlineVersion() 329 Build.VERSION.SDK_INT == 30 -> 1 330 else -> ART_MAINLINE_VERSION_UNDETECTED 331 } 332 willMethodTracingAffectMeasurementsnull333 fun willMethodTracingAffectMeasurements(sdkInt: Int, artVersion: Long): Boolean = 334 sdkInt in 26..30 || // b/313868903 335 artVersion in ART_MAINLINE_VERSIONS_AFFECTING_METHOD_TRACING || // b/303660864 336 (sdkInt == 34 && artVersion >= ART_MAINLINE_INTERNAL_BUILD_MIN) // b/303686344#comment31 337 338 val methodTracingAffectsMeasurements = 339 willMethodTracingAffectMeasurements(Build.VERSION.SDK_INT, artMainlineVersion) 340 341 fun isClassLoadTracingAvailable(sdkInt: Int, artVersion: Long?): Boolean = 342 sdkInt >= 35 || 343 (sdkInt >= 31 && 344 (artVersion == null || artVersion >= ART_MAINLINE_MIN_VERSION_CLASS_LOAD_TRACING)) 345 346 val supportsClassLoadTracing = 347 isClassLoadTracingAvailable(Build.VERSION.SDK_INT, artMainlineVersion) 348 349 val supportsRuntimeImages = 350 Build.VERSION.SDK_INT >= 34 || artMainlineVersion >= ART_MAINLINE_MIN_VERSION_RUNTIME_IMAGE 351 352 val verifyClearsRuntimeImage = 353 Build.VERSION.SDK_INT >= 35 || 354 (Build.VERSION.SDK_INT == 34 && 355 artMainlineVersion >= ART_MAINLINE_MIN_VERSION_VERIFY_CLEARS_RUNTIME_IMAGE) 356 357 val supportsCpuEventCounters = 358 Build.VERSION.SDK_INT < CpuEventCounter.MIN_API_ROOT_REQUIRED || isRooted 359 } 360