1 /* <lambda>null2 * Copyright (C) 2020 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.ParsingPackageImpl 23 import android.content.pm.parsing.ParsingPackageRead 24 import android.content.pm.parsing.ParsingPackageUtils 25 import android.content.pm.parsing.result.ParseInput 26 import android.content.pm.parsing.result.ParseTypeImpl 27 import android.content.res.TypedArray 28 import android.perftests.utils.BenchmarkState 29 import android.perftests.utils.PerfStatusReporter 30 import androidx.test.filters.LargeTest 31 import com.android.internal.util.ConcurrentUtils 32 import libcore.io.IoUtils 33 import org.junit.Rule 34 import org.junit.Test 35 import org.junit.rules.TemporaryFolder 36 import org.junit.runner.RunWith 37 import org.junit.runners.Parameterized 38 import java.io.File 39 import java.io.FileOutputStream 40 import java.util.concurrent.ArrayBlockingQueue 41 import java.util.concurrent.TimeUnit 42 43 @LargeTest 44 @RunWith(Parameterized::class) 45 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<ParsingPackageRead>(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(false, null, null, emptyList(), 200 object : ParsingPackageUtils.Callback { 201 override fun hasFeature(feature: String) = true 202 203 override fun startParsingPackage( 204 packageName: String, 205 baseApkPath: String, 206 path: String, 207 manifestArray: TypedArray, 208 isCoreApp: Boolean 209 ) = ParsingPackageImpl(packageName, baseApkPath, path, manifestArray) 210 }) 211 212 override fun parseImpl(file: File) = 213 parser.parsePackage(input.get()!!.reset(), file, 0).result 214 } 215 216 abstract class PackageCacher<PackageType : Parcelable>(private val cacheDir: File) { 217 218 fun getCachedResult(file: File): PackageType? { 219 val cacheFile = File(cacheDir, file.name) 220 if (!cacheFile.exists()) { 221 return null 222 } 223 224 val bytes = IoUtils.readFileAsByteArray(cacheFile.absolutePath) 225 val parcel = Parcel.obtain().apply { 226 unmarshall(bytes, 0, bytes.size) 227 setDataPosition(0) 228 } 229 ReadHelper(parcel).apply { startAndInstall() } 230 return fromParcel(parcel).also { 231 parcel.recycle() 232 } 233 } 234 235 fun cacheResult(file: File, parsed: Parcelable) { 236 val cacheFile = File(cacheDir, file.name) 237 if (cacheFile.exists()) { 238 if (!cacheFile.delete()) { 239 throw IllegalStateException("Unable to delete cache file: $cacheFile") 240 } 241 } 242 val cacheEntry = toCacheEntry(parsed) 243 return FileOutputStream(cacheFile).use { fos -> fos.write(cacheEntry) } 244 } 245 246 private fun toCacheEntry(pkg: Parcelable): ByteArray { 247 val parcel = Parcel.obtain() 248 val helper = WriteHelper(parcel) 249 pkg.writeToParcel(parcel, 0 /* flags */) 250 helper.finishAndUninstall() 251 return parcel.marshall().also { 252 parcel.recycle() 253 } 254 } 255 256 protected abstract fun fromParcel(parcel: Parcel): PackageType 257 } 258 259 /** 260 * Re-implementation of v1's cache, since that's gone in R+. 261 */ 262 class PackageCacher1(cacheDir: File) : PackageCacher<PackageParser.Package>(cacheDir) { 263 override fun fromParcel(parcel: Parcel) = PackageParser.Package(parcel) 264 } 265 266 /** 267 * Re-implementation of the server side PackageCacher, as it's inaccessible here. 268 */ 269 class PackageCacher2(cacheDir: File) : PackageCacher<ParsingPackageRead>(cacheDir) { 270 override fun fromParcel(parcel: Parcel) = ParsingPackageImpl(parcel) 271 } 272 } 273