Add package parsing v1 vs v2 benchmark
Creates benchmarks for package parsing v1 vs v2, with variants for cached vs non-cached and sequential vs parallelized. The code used is copied from PackageCacher and the old PackageParser implementation, since they are not accessible to the test class. Bug: 141922546 Test: atest PackageParsingPerfTest Change-Id: I1777f84cb54d1ad5ae0dfd32a3461c1b07eef8e0
This commit is contained in:
34
apct-tests/perftests/core/Android.bp
Normal file
34
apct-tests/perftests/core/Android.bp
Normal file
@@ -0,0 +1,34 @@
|
||||
android_test {
|
||||
name: "CorePerfTests",
|
||||
|
||||
resource_dirs: ["res"],
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
"src/**/*.kt",
|
||||
"src/android/os/ISomeService.aidl",
|
||||
],
|
||||
|
||||
static_libs: [
|
||||
"androidx.appcompat_appcompat",
|
||||
"androidx.test.rules",
|
||||
"androidx.annotation_annotation",
|
||||
"apct-perftests-overlay-apps",
|
||||
"apct-perftests-resources-manager-apps",
|
||||
"apct-perftests-utils",
|
||||
"guava",
|
||||
],
|
||||
|
||||
libs: ["android.test.base"],
|
||||
|
||||
platform_apis: true,
|
||||
|
||||
jni_libs: ["libperftestscore_jni"],
|
||||
|
||||
// Use google-fonts/dancing-script for the performance metrics
|
||||
// ANDROIDMK TRANSLATION ERROR: Only $(LOCAL_PATH)/.. values are allowed
|
||||
// LOCAL_ASSET_DIR := $(TOP)/external/google-fonts/dancing-script
|
||||
|
||||
test_suites: ["device-tests"],
|
||||
certificate: "platform",
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE_TAGS := tests
|
||||
|
||||
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
|
||||
LOCAL_SRC_FILES := \
|
||||
$(call all-java-files-under, src) \
|
||||
src/android/os/ISomeService.aidl
|
||||
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := \
|
||||
androidx.appcompat_appcompat \
|
||||
androidx.test.rules \
|
||||
androidx.annotation_annotation \
|
||||
apct-perftests-overlay-apps \
|
||||
apct-perftests-resources-manager-apps \
|
||||
apct-perftests-utils \
|
||||
guava
|
||||
|
||||
LOCAL_JAVA_LIBRARIES := android.test.base
|
||||
|
||||
LOCAL_PACKAGE_NAME := CorePerfTests
|
||||
LOCAL_PRIVATE_PLATFORM_APIS := true
|
||||
|
||||
LOCAL_JNI_SHARED_LIBRARIES := libperftestscore_jni
|
||||
|
||||
# Use google-fonts/dancing-script for the performance metrics
|
||||
LOCAL_ASSET_DIR := $(TOP)/external/google-fonts/dancing-script
|
||||
|
||||
LOCAL_COMPATIBILITY_SUITE += device-tests
|
||||
LOCAL_CERTIFICATE := platform
|
||||
|
||||
include $(BUILD_PACKAGE)
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.os
|
||||
|
||||
import android.content.pm.PackageParser
|
||||
import android.content.pm.PackageParserCacheHelper.ReadHelper
|
||||
import android.content.pm.PackageParserCacheHelper.WriteHelper
|
||||
import android.content.pm.parsing.ParsingPackageImpl
|
||||
import android.content.pm.parsing.ParsingPackageRead
|
||||
import android.content.pm.parsing.ParsingPackageUtils
|
||||
import android.content.pm.parsing.result.ParseTypeImpl
|
||||
import android.content.res.TypedArray
|
||||
import android.perftests.utils.BenchmarkState
|
||||
import android.perftests.utils.PerfStatusReporter
|
||||
import androidx.test.filters.LargeTest
|
||||
import com.android.internal.util.ConcurrentUtils
|
||||
import libcore.io.IoUtils
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.util.concurrent.ArrayBlockingQueue
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@LargeTest
|
||||
@RunWith(Parameterized::class)
|
||||
class PackageParsingPerfTest {
|
||||
|
||||
companion object {
|
||||
private const val PARALLEL_QUEUE_CAPACITY = 10
|
||||
private const val PARALLEL_MAX_THREADS = 4
|
||||
|
||||
private const val QUEUE_POLL_TIMEOUT_SECONDS = 5L
|
||||
|
||||
// TODO: Replace this with core version of SYSTEM_PARTITIONS
|
||||
val FOLDERS_TO_TEST = listOf(
|
||||
Environment.getRootDirectory(),
|
||||
Environment.getVendorDirectory(),
|
||||
Environment.getOdmDirectory(),
|
||||
Environment.getOemDirectory(),
|
||||
Environment.getOemDirectory(),
|
||||
Environment.getSystemExtDirectory()
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters(name = "{0}")
|
||||
fun parameters(): Array<Params> {
|
||||
val apks = FOLDERS_TO_TEST
|
||||
.filter(File::exists)
|
||||
.map(File::walkTopDown)
|
||||
.flatMap(Sequence<File>::asIterable)
|
||||
.filter { it.name.endsWith(".apk") }
|
||||
|
||||
return arrayOf(
|
||||
Params(1, apks) { ParallelParser1(it?.let(::PackageCacher1)) },
|
||||
Params(2, apks) { ParallelParser2(it?.let(::PackageCacher2)) }
|
||||
)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val version: Int,
|
||||
val apks: List<File>,
|
||||
val cacheDirToParser: (File?) -> ParallelParser<*>
|
||||
) {
|
||||
// For test name formatting
|
||||
override fun toString() = "v$version"
|
||||
}
|
||||
}
|
||||
|
||||
@get:Rule
|
||||
var perfStatusReporter = PerfStatusReporter()
|
||||
|
||||
@get:Rule
|
||||
var testFolder = TemporaryFolder()
|
||||
|
||||
@Parameterized.Parameter(0)
|
||||
lateinit var params: Params
|
||||
|
||||
private val state: BenchmarkState get() = perfStatusReporter.benchmarkState
|
||||
private val apks: List<File> get() = params.apks
|
||||
|
||||
@Test
|
||||
fun sequentialNoCache() {
|
||||
params.cacheDirToParser(null).use { parser ->
|
||||
while (state.keepRunning()) {
|
||||
apks.forEach { parser.parse(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sequentialCached() {
|
||||
params.cacheDirToParser(testFolder.newFolder()).use { parser ->
|
||||
// Fill the cache
|
||||
apks.forEach { parser.parse(it) }
|
||||
|
||||
while (state.keepRunning()) {
|
||||
apks.forEach { parser.parse(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parallelNoCache() {
|
||||
params.cacheDirToParser(null).use { parser ->
|
||||
while (state.keepRunning()) {
|
||||
apks.forEach { parser.submit(it) }
|
||||
repeat(apks.size) { parser.take() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parallelCached() {
|
||||
params.cacheDirToParser(testFolder.newFolder()).use { parser ->
|
||||
// Fill the cache
|
||||
apks.forEach { parser.parse(it) }
|
||||
|
||||
while (state.keepRunning()) {
|
||||
apks.forEach { parser.submit(it) }
|
||||
repeat(apks.size) { parser.take() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ParallelParser<PackageType : Parcelable>(
|
||||
private val cacher: PackageCacher<PackageType>? = null
|
||||
) : AutoCloseable {
|
||||
private val queue = ArrayBlockingQueue<Any>(PARALLEL_QUEUE_CAPACITY)
|
||||
private val service = ConcurrentUtils.newFixedThreadPool(
|
||||
PARALLEL_MAX_THREADS, "package-parsing-test",
|
||||
Process.THREAD_PRIORITY_FOREGROUND)
|
||||
|
||||
fun submit(file: File) = service.submit { queue.put(parse(file)) }
|
||||
|
||||
fun take() = queue.poll(QUEUE_POLL_TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||
|
||||
override fun close() {
|
||||
service.shutdownNow()
|
||||
}
|
||||
|
||||
fun parse(file: File) = cacher?.getCachedResult(file)
|
||||
?: parseImpl(file).also { cacher?.cacheResult(file, it) }
|
||||
|
||||
protected abstract fun parseImpl(file: File): PackageType
|
||||
}
|
||||
|
||||
class ParallelParser1(private val cacher: PackageCacher1? = null)
|
||||
: ParallelParser<PackageParser.Package>(cacher) {
|
||||
val parser = PackageParser().apply {
|
||||
setCallback { true }
|
||||
}
|
||||
|
||||
override fun parseImpl(file: File) = parser.parsePackage(file, 0, cacher != null)
|
||||
}
|
||||
|
||||
class ParallelParser2(cacher: PackageCacher2? = null)
|
||||
: ParallelParser<ParsingPackageRead>(cacher) {
|
||||
val input = ThreadLocal.withInitial { ParseTypeImpl() }
|
||||
val parser = ParsingPackageUtils(false, null, null,
|
||||
object : ParsingPackageUtils.Callback {
|
||||
override fun hasFeature(feature: String) = true
|
||||
|
||||
override fun startParsingPackage(
|
||||
packageName: String,
|
||||
baseCodePath: String,
|
||||
codePath: String,
|
||||
manifestArray: TypedArray,
|
||||
isCoreApp: Boolean
|
||||
) = ParsingPackageImpl(packageName, baseCodePath, codePath, manifestArray)
|
||||
})
|
||||
|
||||
override fun parseImpl(file: File) =
|
||||
parser.parsePackage(input.get()!!.reset(), file, 0).result
|
||||
}
|
||||
|
||||
abstract class PackageCacher<PackageType : Parcelable>(private val cacheDir: File) {
|
||||
|
||||
fun getCachedResult(file: File): PackageType? {
|
||||
val cacheFile = File(cacheDir, file.name)
|
||||
if (!cacheFile.exists()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val bytes = IoUtils.readFileAsByteArray(cacheFile.absolutePath)
|
||||
val parcel = Parcel.obtain().apply {
|
||||
unmarshall(bytes, 0, bytes.size)
|
||||
setDataPosition(0)
|
||||
}
|
||||
ReadHelper(parcel).apply { startAndInstall() }
|
||||
return fromParcel(parcel).also {
|
||||
parcel.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
fun cacheResult(file: File, parsed: Parcelable) {
|
||||
val cacheFile = File(cacheDir, file.name)
|
||||
if (cacheFile.exists()) {
|
||||
if (!cacheFile.delete()) {
|
||||
throw IllegalStateException("Unable to delete cache file: $cacheFile")
|
||||
}
|
||||
}
|
||||
val cacheEntry = toCacheEntry(parsed)
|
||||
return FileOutputStream(cacheFile).use { fos -> fos.write(cacheEntry) }
|
||||
}
|
||||
|
||||
private fun toCacheEntry(pkg: Parcelable): ByteArray {
|
||||
val parcel = Parcel.obtain()
|
||||
val helper = WriteHelper(parcel)
|
||||
pkg.writeToParcel(parcel, 0 /* flags */)
|
||||
helper.finishAndUninstall()
|
||||
return parcel.marshall().also {
|
||||
parcel.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun fromParcel(parcel: Parcel): PackageType
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-implementation of v1's cache, since that's gone in R+.
|
||||
*/
|
||||
class PackageCacher1(cacheDir: File) : PackageCacher<PackageParser.Package>(cacheDir) {
|
||||
override fun fromParcel(parcel: Parcel) = PackageParser.Package(parcel)
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-implementation of the server side PackageCacher, as it's inaccessible here.
|
||||
*/
|
||||
class PackageCacher2(cacheDir: File) : PackageCacher<ParsingPackageRead>(cacheDir) {
|
||||
override fun fromParcel(parcel: Parcel) = ParsingPackageImpl(parcel)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user