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