blob: 9e463652d0b60b9399183174a72c16c2ddfb4727 [file] [log] [blame]
Winson04fd0362020-02-13 18:08:32 -08001/*
2 * 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
17package android.os
18
19import android.content.pm.PackageParser
20import android.content.pm.PackageParserCacheHelper.ReadHelper
21import android.content.pm.PackageParserCacheHelper.WriteHelper
22import android.content.pm.parsing.ParsingPackageImpl
23import android.content.pm.parsing.ParsingPackageRead
24import android.content.pm.parsing.ParsingPackageUtils
25import android.content.pm.parsing.result.ParseTypeImpl
26import android.content.res.TypedArray
27import android.perftests.utils.BenchmarkState
28import android.perftests.utils.PerfStatusReporter
29import androidx.test.filters.LargeTest
30import com.android.internal.util.ConcurrentUtils
31import libcore.io.IoUtils
32import org.junit.Rule
33import org.junit.Test
34import org.junit.rules.TemporaryFolder
35import org.junit.runner.RunWith
36import org.junit.runners.Parameterized
37import java.io.File
38import java.io.FileOutputStream
39import java.util.concurrent.ArrayBlockingQueue
40import java.util.concurrent.TimeUnit
41
42@LargeTest
43@RunWith(Parameterized::class)
44class 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 @Test
100 fun sequentialNoCache() {
101 params.cacheDirToParser(null).use { parser ->
102 while (state.keepRunning()) {
103 apks.forEach { parser.parse(it) }
104 }
105 }
106 }
107
108 @Test
109 fun sequentialCached() {
110 params.cacheDirToParser(testFolder.newFolder()).use { parser ->
111 // Fill the cache
112 apks.forEach { parser.parse(it) }
113
114 while (state.keepRunning()) {
115 apks.forEach { parser.parse(it) }
116 }
117 }
118 }
119
120 @Test
121 fun parallelNoCache() {
122 params.cacheDirToParser(null).use { parser ->
123 while (state.keepRunning()) {
124 apks.forEach { parser.submit(it) }
125 repeat(apks.size) { parser.take() }
126 }
127 }
128 }
129
130 @Test
131 fun parallelCached() {
132 params.cacheDirToParser(testFolder.newFolder()).use { parser ->
133 // Fill the cache
134 apks.forEach { parser.parse(it) }
135
136 while (state.keepRunning()) {
137 apks.forEach { parser.submit(it) }
138 repeat(apks.size) { parser.take() }
139 }
140 }
141 }
142
143 abstract class ParallelParser<PackageType : Parcelable>(
144 private val cacher: PackageCacher<PackageType>? = null
145 ) : AutoCloseable {
146 private val queue = ArrayBlockingQueue<Any>(PARALLEL_QUEUE_CAPACITY)
147 private val service = ConcurrentUtils.newFixedThreadPool(
148 PARALLEL_MAX_THREADS, "package-parsing-test",
149 Process.THREAD_PRIORITY_FOREGROUND)
150
151 fun submit(file: File) = service.submit { queue.put(parse(file)) }
152
153 fun take() = queue.poll(QUEUE_POLL_TIMEOUT_SECONDS, TimeUnit.SECONDS)
154
155 override fun close() {
156 service.shutdownNow()
157 }
158
159 fun parse(file: File) = cacher?.getCachedResult(file)
160 ?: parseImpl(file).also { cacher?.cacheResult(file, it) }
161
162 protected abstract fun parseImpl(file: File): PackageType
163 }
164
165 class ParallelParser1(private val cacher: PackageCacher1? = null)
166 : ParallelParser<PackageParser.Package>(cacher) {
167 val parser = PackageParser().apply {
168 setCallback { true }
169 }
170
171 override fun parseImpl(file: File) = parser.parsePackage(file, 0, cacher != null)
172 }
173
174 class ParallelParser2(cacher: PackageCacher2? = null)
175 : ParallelParser<ParsingPackageRead>(cacher) {
176 val input = ThreadLocal.withInitial { ParseTypeImpl() }
177 val parser = ParsingPackageUtils(false, null, null,
178 object : ParsingPackageUtils.Callback {
179 override fun hasFeature(feature: String) = true
180
181 override fun startParsingPackage(
182 packageName: String,
183 baseCodePath: String,
184 codePath: String,
185 manifestArray: TypedArray,
186 isCoreApp: Boolean
187 ) = ParsingPackageImpl(packageName, baseCodePath, codePath, manifestArray)
188 })
189
190 override fun parseImpl(file: File) =
191 parser.parsePackage(input.get()!!.reset(), file, 0).result
192 }
193
194 abstract class PackageCacher<PackageType : Parcelable>(private val cacheDir: File) {
195
196 fun getCachedResult(file: File): PackageType? {
197 val cacheFile = File(cacheDir, file.name)
198 if (!cacheFile.exists()) {
199 return null
200 }
201
202 val bytes = IoUtils.readFileAsByteArray(cacheFile.absolutePath)
203 val parcel = Parcel.obtain().apply {
204 unmarshall(bytes, 0, bytes.size)
205 setDataPosition(0)
206 }
207 ReadHelper(parcel).apply { startAndInstall() }
208 return fromParcel(parcel).also {
209 parcel.recycle()
210 }
211 }
212
213 fun cacheResult(file: File, parsed: Parcelable) {
214 val cacheFile = File(cacheDir, file.name)
215 if (cacheFile.exists()) {
216 if (!cacheFile.delete()) {
217 throw IllegalStateException("Unable to delete cache file: $cacheFile")
218 }
219 }
220 val cacheEntry = toCacheEntry(parsed)
221 return FileOutputStream(cacheFile).use { fos -> fos.write(cacheEntry) }
222 }
223
224 private fun toCacheEntry(pkg: Parcelable): ByteArray {
225 val parcel = Parcel.obtain()
226 val helper = WriteHelper(parcel)
227 pkg.writeToParcel(parcel, 0 /* flags */)
228 helper.finishAndUninstall()
229 return parcel.marshall().also {
230 parcel.recycle()
231 }
232 }
233
234 protected abstract fun fromParcel(parcel: Parcel): PackageType
235 }
236
237 /**
238 * Re-implementation of v1's cache, since that's gone in R+.
239 */
240 class PackageCacher1(cacheDir: File) : PackageCacher<PackageParser.Package>(cacheDir) {
241 override fun fromParcel(parcel: Parcel) = PackageParser.Package(parcel)
242 }
243
244 /**
245 * Re-implementation of the server side PackageCacher, as it's inaccessible here.
246 */
247 class PackageCacher2(cacheDir: File) : PackageCacher<ParsingPackageRead>(cacheDir) {
248 override fun fromParcel(parcel: Parcel) = ParsingPackageImpl(parcel)
249 }
250}