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