• 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 = 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<PackageImpl>(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(null,
200             null,
201             emptyList(),
202             object :
203                 ParsingPackageUtils.Callback {
204                 override fun hasFeature(feature: String) = true
205 
206                 override fun startParsingPackage(
207                     packageName: String,
208                     baseApkPath: String,
209                     path: String,
210                     manifestArray: TypedArray,
211                     isCoreApp: Boolean
212                 ) = PackageImpl(
213                     packageName,
214                     baseApkPath,
215                     path,
216                     manifestArray,
217                     isCoreApp,
218                     this,
219                 )
220                 override fun getHiddenApiWhitelistedApps() =
221                         SystemConfig.getInstance().hiddenApiWhitelistedApps
222                 override fun getInstallConstraintsAllowlist() =
223                         SystemConfig.getInstance().installConstraintsAllowlist
224             })
225 
226         override fun parseImpl(file: File) =
227                 parser.parsePackage(input.get()!!.reset(), file, 0).result
228                         as PackageImpl
229     }
230 
231     abstract class PackageCacher<PackageType : Parcelable>(private val cacheDir: File) {
232 
233         fun getCachedResult(file: File): PackageType? {
234             val cacheFile = File(cacheDir, file.name)
235             if (!cacheFile.exists()) {
236                 return null
237             }
238 
239             val bytes = IoUtils.readFileAsByteArray(cacheFile.absolutePath)
240             val parcel = Parcel.obtain().apply {
241                 unmarshall(bytes, 0, bytes.size)
242                 setDataPosition(0)
243             }
244             ReadHelper(parcel).apply { startAndInstall() }
245             return fromParcel(parcel).also {
246                 parcel.recycle()
247             }
248         }
249 
250         fun cacheResult(file: File, parsed: Parcelable) {
251             val cacheFile = File(cacheDir, file.name)
252             if (cacheFile.exists()) {
253                 if (!cacheFile.delete()) {
254                     throw IllegalStateException("Unable to delete cache file: $cacheFile")
255                 }
256             }
257             val cacheEntry = toCacheEntry(parsed)
258             return FileOutputStream(cacheFile).use { fos -> fos.write(cacheEntry) }
259         }
260 
261         private fun toCacheEntry(pkg: Parcelable): ByteArray {
262             val parcel = Parcel.obtain()
263             val helper = WriteHelper(parcel)
264             pkg.writeToParcel(parcel, 0 /* flags */)
265             helper.finishAndUninstall()
266             return parcel.marshall().also {
267                 parcel.recycle()
268             }
269         }
270 
271         protected abstract fun fromParcel(parcel: Parcel): PackageType
272     }
273 
274     /**
275      * Re-implementation of v1's cache, since that's gone in R+.
276      */
277     class PackageCacher1(cacheDir: File) : PackageCacher<PackageParser.Package>(cacheDir) {
278         override fun fromParcel(parcel: Parcel) = PackageParser.Package(parcel)
279     }
280 
281     /**
282      * Re-implementation of the server side PackageCacher, as it's inaccessible here.
283      */
284     class PackageCacher2(cacheDir: File) : PackageCacher<PackageImpl>(cacheDir) {
285         override fun fromParcel(parcel: Parcel) =
286             PackageImpl(parcel)
287     }
288 }
289