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 @Test 101 fun sequentialNoCache() { 102 params.cacheDirToParser(null).use { parser -> 103 while (state.keepRunning()) { 104 apks.forEach { parser.parse(it) } 105 } 106 } 107 } 108 109 @Test 110 fun sequentialCached() { 111 params.cacheDirToParser(testFolder.newFolder()).use { parser -> 112 // Fill the cache 113 apks.forEach { parser.parse(it) } 114 115 while (state.keepRunning()) { 116 apks.forEach { parser.parse(it) } 117 } 118 } 119 } 120 121 @Test 122 fun parallelNoCache() { 123 params.cacheDirToParser(null).use { parser -> 124 while (state.keepRunning()) { 125 apks.forEach { parser.submit(it) } 126 repeat(apks.size) { parser.take() } 127 } 128 } 129 } 130 131 @Test 132 fun parallelCached() { 133 params.cacheDirToParser(testFolder.newFolder()).use { parser -> 134 // Fill the cache 135 apks.forEach { parser.parse(it) } 136 137 while (state.keepRunning()) { 138 apks.forEach { parser.submit(it) } 139 repeat(apks.size) { parser.take() } 140 } 141 } 142 } 143 144 abstract class ParallelParser<PackageType : Parcelable>( 145 private val cacher: PackageCacher<PackageType>? = null 146 ) : AutoCloseable { 147 private val queue = ArrayBlockingQueue<Any>(PARALLEL_QUEUE_CAPACITY) 148 private val service = ConcurrentUtils.newFixedThreadPool( 149 PARALLEL_MAX_THREADS, "package-parsing-test", 150 Process.THREAD_PRIORITY_FOREGROUND) 151 152 fun submit(file: File) = service.submit { queue.put(parse(file)) } 153 154 fun take() = queue.poll(QUEUE_POLL_TIMEOUT_SECONDS, TimeUnit.SECONDS) 155 156 override fun close() { 157 service.shutdownNow() 158 } 159 160 fun parse(file: File) = cacher?.getCachedResult(file) 161 ?: parseImpl(file).also { cacher?.cacheResult(file, it) } 162 163 protected abstract fun parseImpl(file: File): PackageType 164 } 165 166 class ParallelParser1(private val cacher: PackageCacher1? = null) 167 : ParallelParser<PackageParser.Package>(cacher) { 168 val parser = PackageParser().apply { 169 setCallback { true } 170 } 171 172 override fun parseImpl(file: File) = parser.parsePackage(file, 0, cacher != null) 173 } 174 175 class ParallelParser2(cacher: PackageCacher2? = null) 176 : ParallelParser<ParsingPackageRead>(cacher) { 177 val input = ThreadLocal.withInitial { 178 // For testing, just disable enforcement to avoid hooking up to compat framework 179 ParseTypeImpl(ParseInput.Callback { _, _, _ -> false }) 180 } 181 val parser = ParsingPackageUtils(false, null, null, 182 object : ParsingPackageUtils.Callback { 183 override fun hasFeature(feature: String) = true 184 185 override fun startParsingPackage( 186 packageName: String, 187 baseCodePath: String, 188 codePath: String, 189 manifestArray: TypedArray, 190 isCoreApp: Boolean 191 ) = ParsingPackageImpl(packageName, baseCodePath, codePath, manifestArray) 192 }) 193 194 override fun parseImpl(file: File) = 195 parser.parsePackage(input.get()!!.reset(), file, 0).result 196 } 197 198 abstract class PackageCacher<PackageType : Parcelable>(private val cacheDir: File) { 199 200 fun getCachedResult(file: File): PackageType? { 201 val cacheFile = File(cacheDir, file.name) 202 if (!cacheFile.exists()) { 203 return null 204 } 205 206 val bytes = IoUtils.readFileAsByteArray(cacheFile.absolutePath) 207 val parcel = Parcel.obtain().apply { 208 unmarshall(bytes, 0, bytes.size) 209 setDataPosition(0) 210 } 211 ReadHelper(parcel).apply { startAndInstall() } 212 return fromParcel(parcel).also { 213 parcel.recycle() 214 } 215 } 216 217 fun cacheResult(file: File, parsed: Parcelable) { 218 val cacheFile = File(cacheDir, file.name) 219 if (cacheFile.exists()) { 220 if (!cacheFile.delete()) { 221 throw IllegalStateException("Unable to delete cache file: $cacheFile") 222 } 223 } 224 val cacheEntry = toCacheEntry(parsed) 225 return FileOutputStream(cacheFile).use { fos -> fos.write(cacheEntry) } 226 } 227 228 private fun toCacheEntry(pkg: Parcelable): ByteArray { 229 val parcel = Parcel.obtain() 230 val helper = WriteHelper(parcel) 231 pkg.writeToParcel(parcel, 0 /* flags */) 232 helper.finishAndUninstall() 233 return parcel.marshall().also { 234 parcel.recycle() 235 } 236 } 237 238 protected abstract fun fromParcel(parcel: Parcel): PackageType 239 } 240 241 /** 242 * Re-implementation of v1's cache, since that's gone in R+. 243 */ 244 class PackageCacher1(cacheDir: File) : PackageCacher<PackageParser.Package>(cacheDir) { 245 override fun fromParcel(parcel: Parcel) = PackageParser.Package(parcel) 246 } 247 248 /** 249 * Re-implementation of the server side PackageCacher, as it's inaccessible here. 250 */ 251 class PackageCacher2(cacheDir: File) : PackageCacher<ParsingPackageRead>(cacheDir) { 252 override fun fromParcel(parcel: Parcel) = ParsingPackageImpl(parcel) 253 } 254 } 255