• 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     @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) {
177         val input = ThreadLocal.withInitial {
178             // For testing, just disable enforcement to avoid hooking up to compat framework
179             ParseTypeImpl(ParseInput.Callback { _, _, _ -> false })
180         }
181         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 }
255