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