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