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