blob: 29721c59364681d27a9c6fec21eb1207ee8c8009 [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
Winson727da642020-03-10 15:25:32 -070025import android.content.pm.parsing.result.ParseInput
Winson04fd0362020-02-13 18:08:32 -080026import android.content.pm.parsing.result.ParseTypeImpl
27import android.content.res.TypedArray
28import android.perftests.utils.BenchmarkState
29import android.perftests.utils.PerfStatusReporter
30import androidx.test.filters.LargeTest
31import com.android.internal.util.ConcurrentUtils
32import libcore.io.IoUtils
33import org.junit.Rule
34import org.junit.Test
35import org.junit.rules.TemporaryFolder
36import org.junit.runner.RunWith
37import org.junit.runners.Parameterized
38import java.io.File
39import java.io.FileOutputStream
40import java.util.concurrent.ArrayBlockingQueue
41import java.util.concurrent.TimeUnit
42
43@LargeTest
44@RunWith(Parameterized::class)
45class 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) {
Winson727da642020-03-10 15:25:32 -0700177 val input = ThreadLocal.withInitial {
178 // For testing, just disable enforcement to avoid hooking up to compat framework
179 ParseTypeImpl(ParseInput.Callback { _, _, _ -> false })
180 }
Winson04fd0362020-02-13 18:08:32 -0800181 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}