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:
Winson
2020-02-13 18:08:32 -08:00
parent 27f83cdbce
commit 04fd0366bd
3 changed files with 284 additions and 33 deletions

View 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",
}

View File

@@ -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)

View File

@@ -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)
}
}