1 /* <lambda>null2 * Copyright (C) 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 android.os 18 19 import android.content.pm.PackageParser 20 import android.content.pm.PackageParserCacheHelper.ReadHelper 21 import android.content.pm.PackageParserCacheHelper.WriteHelper 22 import android.content.pm.parsing.result.ParseInput 23 import android.content.pm.parsing.result.ParseTypeImpl 24 import android.content.res.TypedArray 25 import android.perftests.utils.BenchmarkState 26 import android.perftests.utils.PerfStatusReporter 27 import androidx.test.filters.LargeTest 28 import com.android.internal.pm.parsing.pkg.PackageImpl 29 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils 30 import com.android.internal.util.ConcurrentUtils 31 import com.android.server.SystemConfig 32 import java.io.File 33 import java.io.FileOutputStream 34 import java.util.concurrent.ArrayBlockingQueue 35 import java.util.concurrent.TimeUnit 36 import libcore.io.IoUtils 37 import org.junit.Rule 38 import org.junit.Test 39 import org.junit.rules.TemporaryFolder 40 import org.junit.runner.RunWith 41 import org.junit.runners.Parameterized 42 43 @LargeTest 44 @RunWith(Parameterized::class) 45 public class PackageParsingPerfTest { 46 47 companion object { 48 private const val PARALLEL_QUEUE_CAPACITY = 10 49 private const val PARALLEL_MAX_THREADS = 4 50 51 private const val QUEUE_POLL_TIMEOUT_SECONDS = 5L 52 53 // TODO: Replace this with core version of SYSTEM_PARTITIONS 54 val FOLDERS_TO_TEST = 55 listOf( 56 Environment.getRootDirectory(), 57 Environment.getVendorDirectory(), 58 Environment.getOdmDirectory(), 59 Environment.getOemDirectory(), 60 Environment.getOemDirectory(), 61 Environment.getSystemExtDirectory(), 62 ) 63 64 @JvmStatic 65 @Parameterized.Parameters(name = "{0}") 66 fun parameters(): Array<Params> { 67 val apks = 68 FOLDERS_TO_TEST.filter(File::exists) 69 .map(File::walkTopDown) 70 .flatMap(Sequence<File>::asIterable) 71 .filter { it.name.endsWith(".apk") } 72 73 return arrayOf( 74 Params(1, apks) { ParallelParser1(it?.let(::PackageCacher1)) }, 75 Params(2, apks) { ParallelParser2(it?.let(::PackageCacher2)) }, 76 ) 77 } 78 79 data class Params( 80 val version: Int, 81 val apks: List<File>, 82 val cacheDirToParser: (File?) -> ParallelParser<*>, 83 ) { 84 // For test name formatting 85 override fun toString() = "v$version" 86 } 87 } 88 89 @get:Rule var perfStatusReporter = PerfStatusReporter() 90 91 @get:Rule var testFolder = TemporaryFolder() 92 93 @Parameterized.Parameter(0) lateinit var params: Params 94 95 private val state: BenchmarkState 96 get() = perfStatusReporter.benchmarkState 97 98 private val apks: List<File> 99 get() = params.apks 100 101 private fun safeParse(parser: ParallelParser<*>, file: File) { 102 try { 103 parser.parse(file) 104 } catch (e: Exception) { 105 // ignore 106 } 107 } 108 109 @Test 110 fun sequentialNoCache() { 111 params.cacheDirToParser(null).use { parser -> 112 while (state.keepRunning()) { 113 apks.forEach { safeParse(parser, it) } 114 } 115 } 116 } 117 118 @Test 119 fun sequentialCached() { 120 params.cacheDirToParser(testFolder.newFolder()).use { parser -> 121 // Fill the cache 122 apks.forEach { safeParse(parser, it) } 123 124 while (state.keepRunning()) { 125 apks.forEach { safeParse(parser, it) } 126 } 127 } 128 } 129 130 @Test 131 fun parallelNoCache() { 132 params.cacheDirToParser(null).use { parser -> 133 while (state.keepRunning()) { 134 apks.forEach { parser.submit(it) } 135 repeat(apks.size) { parser.take() } 136 } 137 } 138 } 139 140 @Test 141 fun parallelCached() { 142 params.cacheDirToParser(testFolder.newFolder()).use { parser -> 143 // Fill the cache 144 apks.forEach { safeParse(parser, it) } 145 146 while (state.keepRunning()) { 147 apks.forEach { parser.submit(it) } 148 repeat(apks.size) { parser.take() } 149 } 150 } 151 } 152 153 abstract class ParallelParser<PackageType : Parcelable>( 154 private val cacher: PackageCacher<PackageType>? = null 155 ) : AutoCloseable { 156 private val queue = ArrayBlockingQueue<Any>(PARALLEL_QUEUE_CAPACITY) 157 private val service = 158 ConcurrentUtils.newFixedThreadPool( 159 PARALLEL_MAX_THREADS, 160 "package-parsing-test", 161 Process.THREAD_PRIORITY_FOREGROUND, 162 ) 163 164 fun submit(file: File) { 165 service.submit { 166 try { 167 queue.put(parse(file)) 168 } catch (e: Exception) { 169 queue.put(e) 170 } 171 } 172 } 173 174 fun take() = queue.poll(QUEUE_POLL_TIMEOUT_SECONDS, TimeUnit.SECONDS) 175 176 override fun close() { 177 service.shutdownNow() 178 } 179 180 fun parse(file: File) = 181 cacher?.getCachedResult(file) ?: parseImpl(file).also { cacher?.cacheResult(file, it) } 182 183 protected abstract fun parseImpl(file: File): PackageType 184 } 185 186 class ParallelParser1(private val cacher: PackageCacher1? = null) : 187 ParallelParser<PackageParser.Package>(cacher) { 188 val parser = PackageParser().apply { setCallback { true } } 189 190 override fun parseImpl(file: File) = parser.parsePackage(file, 0, cacher != null) 191 } 192 193 class ParallelParser2(cacher: PackageCacher2? = null) : ParallelParser<PackageImpl>(cacher) { 194 val input = 195 ThreadLocal.withInitial { 196 // For testing, just disable enforcement to avoid hooking up to compat framework 197 ParseTypeImpl(ParseInput.Callback { _, _, _ -> false }) 198 } 199 val parser = 200 ParsingPackageUtils( 201 null, 202 null, 203 emptyList(), 204 object : ParsingPackageUtils.Callback { 205 override fun hasFeature(feature: String) = true 206 207 override fun startParsingPackage( 208 packageName: String, 209 baseApkPath: String, 210 path: String, 211 manifestArray: TypedArray, 212 isCoreApp: Boolean, 213 ) = PackageImpl(packageName, baseApkPath, path, manifestArray, isCoreApp, this) 214 215 override fun getHiddenApiWhitelistedApps() = 216 SystemConfig.getInstance().hiddenApiWhitelistedApps 217 218 override fun getInstallConstraintsAllowlist() = 219 SystemConfig.getInstance().installConstraintsAllowlist 220 }, 221 ) 222 223 override fun parseImpl(file: File) = 224 parser.parsePackage(input.get()!!.reset(), file, 0).result as PackageImpl 225 } 226 227 abstract class PackageCacher<PackageType : Parcelable>(private val cacheDir: File) { 228 229 fun getCachedResult(file: File): PackageType? { 230 val cacheFile = File(cacheDir, file.name) 231 if (!cacheFile.exists()) { 232 return null 233 } 234 235 val bytes = IoUtils.readFileAsByteArray(cacheFile.absolutePath) 236 val parcel = 237 Parcel.obtain().apply { 238 unmarshall(bytes, 0, bytes.size) 239 setDataPosition(0) 240 } 241 ReadHelper(parcel).apply { startAndInstall() } 242 return fromParcel(parcel).also { parcel.recycle() } 243 } 244 245 fun cacheResult(file: File, parsed: Parcelable) { 246 val cacheFile = File(cacheDir, file.name) 247 if (cacheFile.exists()) { 248 if (!cacheFile.delete()) { 249 throw IllegalStateException("Unable to delete cache file: $cacheFile") 250 } 251 } 252 val cacheEntry = toCacheEntry(parsed) 253 return FileOutputStream(cacheFile).use { fos -> fos.write(cacheEntry) } 254 } 255 256 private fun toCacheEntry(pkg: Parcelable): ByteArray { 257 val parcel = Parcel.obtain() 258 val helper = WriteHelper(parcel) 259 pkg.writeToParcel(parcel, 0 /* flags */) 260 helper.finishAndUninstall() 261 return parcel.marshall().also { parcel.recycle() } 262 } 263 264 protected abstract fun fromParcel(parcel: Parcel): PackageType 265 } 266 267 /** Re-implementation of v1's cache, since that's gone in R+. */ 268 class PackageCacher1(cacheDir: File) : PackageCacher<PackageParser.Package>(cacheDir) { 269 override fun fromParcel(parcel: Parcel) = PackageParser.Package(parcel) 270 } 271 272 /** Re-implementation of the server side PackageCacher, as it's inaccessible here. */ 273 class PackageCacher2(cacheDir: File) : PackageCacher<PackageImpl>(cacheDir) { 274 override fun fromParcel(parcel: Parcel) = PackageImpl(parcel) 275 } 276 } 277