| /* |
| * 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.util; |
| |
| import static org.junit.Assert.assertEquals; |
| |
| import android.os.Bundle; |
| import android.os.Debug; |
| import android.perftests.utils.BenchmarkState; |
| import android.perftests.utils.PerfStatusReporter; |
| |
| import androidx.test.InstrumentationRegistry; |
| import androidx.test.filters.LargeTest; |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import com.android.internal.util.HexDump; |
| import com.android.modules.utils.TypedXmlPullParser; |
| import com.android.modules.utils.TypedXmlSerializer; |
| |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.xmlpull.v1.XmlPullParser; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.nio.charset.StandardCharsets; |
| import java.util.function.Supplier; |
| |
| @RunWith(AndroidJUnit4.class) |
| @LargeTest |
| public class XmlPerfTest { |
| /** |
| * Since allocation measurement adds overhead, it's disabled by default for |
| * performance runs. It can be manually enabled to compare GC behavior. |
| */ |
| private static final boolean MEASURE_ALLOC = false; |
| |
| @Rule |
| public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); |
| |
| @Test |
| public void timeWrite_Fast() throws Exception { |
| doWrite(() -> Xml.newFastSerializer()); |
| } |
| |
| @Test |
| public void timeWrite_Binary() throws Exception { |
| doWrite(() -> Xml.newBinarySerializer()); |
| } |
| |
| private void doWrite(Supplier<TypedXmlSerializer> outFactory) throws Exception { |
| if (MEASURE_ALLOC) { |
| Debug.startAllocCounting(); |
| } |
| |
| int iterations = 0; |
| final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); |
| while (state.keepRunning()) { |
| iterations++; |
| try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { |
| final TypedXmlSerializer out = outFactory.get(); |
| out.setOutput(os, StandardCharsets.UTF_8.name()); |
| write(out); |
| } |
| } |
| |
| if (MEASURE_ALLOC) { |
| Debug.stopAllocCounting(); |
| final Bundle results = new Bundle(); |
| results.putLong("threadAllocCount_mean", Debug.getThreadAllocCount() / iterations); |
| results.putLong("threadAllocSize_mean", Debug.getThreadAllocSize() / iterations); |
| InstrumentationRegistry.getInstrumentation().sendStatus(0, results); |
| } |
| } |
| |
| @Test |
| public void timeRead_Fast() throws Exception { |
| doRead(() -> Xml.newFastSerializer(), () -> Xml.newFastPullParser()); |
| } |
| |
| @Test |
| public void timeRead_Binary() throws Exception { |
| doRead(() -> Xml.newBinarySerializer(), () -> Xml.newBinaryPullParser()); |
| } |
| |
| private void doRead(Supplier<TypedXmlSerializer> outFactory, |
| Supplier<TypedXmlPullParser> inFactory) throws Exception { |
| final byte[] raw; |
| try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { |
| TypedXmlSerializer out = outFactory.get(); |
| out.setOutput(os, StandardCharsets.UTF_8.name()); |
| write(out); |
| raw = os.toByteArray(); |
| } |
| |
| if (MEASURE_ALLOC) { |
| Debug.startAllocCounting(); |
| } |
| |
| int iterations = 0; |
| final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); |
| while (state.keepRunning()) { |
| iterations++; |
| try (ByteArrayInputStream is = new ByteArrayInputStream(raw)) { |
| TypedXmlPullParser xml = inFactory.get(); |
| xml.setInput(is, StandardCharsets.UTF_8.name()); |
| read(xml); |
| } |
| } |
| |
| if (MEASURE_ALLOC) { |
| Debug.stopAllocCounting(); |
| final Bundle results = new Bundle(); |
| results.putLong("sizeBytes", raw.length); |
| results.putLong("threadAllocCount_mean", Debug.getThreadAllocCount() / iterations); |
| results.putLong("threadAllocSize_mean", Debug.getThreadAllocSize() / iterations); |
| InstrumentationRegistry.getInstrumentation().sendStatus(0, results); |
| } else { |
| final Bundle results = new Bundle(); |
| results.putLong("sizeBytes", raw.length); |
| InstrumentationRegistry.getInstrumentation().sendStatus(0, results); |
| } |
| } |
| |
| /** |
| * Not even joking, this is a typical public key blob stored in |
| * {@code packages.xml}. |
| */ |
| private static final byte[] KEY_BLOB = HexDump.hexStringToByteArray("" |
| + "308204a830820390a003020102020900a1573d0f45bea193300d06092a864886f70d010105050030819" |
| + "4310b3009060355040613025553311330110603550408130a43616c69666f726e696131163014060355" |
| + "0407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e06035" |
| + "5040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d" |
| + "0109011613616e64726f696440616e64726f69642e636f6d301e170d3131303931393138343232355a1" |
| + "70d3339303230343138343232355a308194310b3009060355040613025553311330110603550408130a" |
| + "43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e0603550" |
| + "40a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e" |
| + "64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d3" |
| + "0820120300d06092a864886f70d01010105000382010d00308201080282010100de1b51336afc909d8b" |
| + "cca5920fcdc8940578ec5c253898930e985481cfdea75ba6fc54b1f7bb492a03d98db471ab4200103a8" |
| + "314e60ee25fef6c8b83bc1b2b45b084874cffef148fa2001bb25c672b6beba50b7ac026b546da762ea2" |
| + "23829a22b80ef286131f059d2c9b4ca71d54e515a8a3fd6bf5f12a2493dfc2619b337b032a7cf8bbd34" |
| + "b833f2b93aeab3d325549a93272093943bb59dfc0197ae4861ff514e019b73f5cf10023ad1a032adb4b" |
| + "9bbaeb4debecb4941d6a02381f1165e1ac884c1fca9525c5854dce2ad8ec839b8ce78442c16367efc07" |
| + "778a337d3ca2cdf9792ac722b95d67c345f1c00976ec372f02bfcbef0262cc512a6845e71cfea0d0201" |
| + "03a381fc3081f9301d0603551d0e0416041478a0fc4517fb70ff52210df33c8d32290a44b2bb3081c90" |
| + "603551d230481c13081be801478a0fc4517fb70ff52210df33c8d32290a44b2bba1819aa48197308194" |
| + "310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550" |
| + "407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355" |
| + "040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0" |
| + "109011613616e64726f696440616e64726f69642e636f6d820900a1573d0f45bea193300c0603551d13" |
| + "040530030101ff300d06092a864886f70d01010505000382010100977302dfbf668d7c61841c9c78d25" |
| + "63bcda1b199e95e6275a799939981416909722713531157f3cdcfea94eea7bb79ca3ca972bd8058a36a" |
| + "d1919291df42d7190678d4ea47a4b9552c9dfb260e6d0d9129b44615cd641c1080580e8a990dd768c6a" |
| + "b500c3b964e185874e4105109d94c5bd8c405deb3cf0f7960a563bfab58169a956372167a7e2674a04c" |
| + "4f80015d8f7869a7a4139aecbbdca2abc294144ee01e4109f0e47a518363cf6e9bf41f7560e94bdd4a5" |
| + "d085234796b05c7a1389adfd489feec2a107955129d7991daa49afb3d327dc0dc4fe959789372b093a8" |
| + "9c8dbfa41554f771c18015a6cb242a17e04d19d55d3b4664eae12caf2a11cd2b836e"); |
| |
| /** |
| * Typical list of permissions referenced in {@code packages.xml}. |
| */ |
| private static final String[] PERMS = new String[] { |
| "android.permission.ACCESS_CACHE_FILESYSTEM", |
| "android.permission.WRITE_SETTINGS", |
| "android.permission.MANAGE_EXTERNAL_STORAGE", |
| "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS", |
| "android.permission.FOREGROUND_SERVICE", |
| "android.permission.RECEIVE_BOOT_COMPLETED", |
| "android.permission.WRITE_MEDIA_STORAGE", |
| "android.permission.INTERNET", |
| "android.permission.UPDATE_DEVICE_STATS", |
| "android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY", |
| "android.permission.MANAGE_USB", |
| "android.permission.ACCESS_ALL_DOWNLOADS", |
| "android.permission.ACCESS_DOWNLOAD_MANAGER", |
| "android.permission.MANAGE_USERS", |
| "android.permission.ACCESS_NETWORK_STATE", |
| "android.permission.ACCESS_MTP", |
| "android.permission.INTERACT_ACROSS_USERS", |
| "android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS", |
| "android.permission.CLEAR_APP_CACHE", |
| "android.permission.CONNECTIVITY_INTERNAL", |
| "android.permission.START_ACTIVITIES_FROM_BACKGROUND", |
| "android.permission.QUERY_ALL_PACKAGES", |
| "android.permission.WAKE_LOCK", |
| "android.permission.UPDATE_APP_OPS_STATS", |
| }; |
| |
| /** |
| * Write a typical {@code packages.xml} file containing 100 applications, |
| * each of which defines signing key and permission information. |
| */ |
| private static void write(TypedXmlSerializer out) throws IOException { |
| out.startDocument(null, true); |
| out.startTag(null, "packages"); |
| for (int i = 0; i < 100; i++) { |
| out.startTag(null, "package"); |
| out.attribute(null, "name", "com.android.providers.media"); |
| out.attribute(null, "codePath", "/system/priv-app/MediaProviderLegacy"); |
| out.attribute(null, "nativeLibraryPath", "/system/priv-app/MediaProviderLegacy/lib"); |
| out.attributeLong(null, "publicFlags", 944258629L); |
| out.attributeLong(null, "privateFlags", -1946152952L); |
| out.attributeLong(null, "ft", 1603899064000L); |
| out.attributeLong(null, "it", 1603899064000L); |
| out.attributeLong(null, "ut", 1603899064000L); |
| out.attributeInt(null, "version", 1024); |
| out.attributeInt(null, "sharedUserId", 10100); |
| out.attributeBoolean(null, "isOrphaned", true); |
| |
| out.startTag(null, "sigs"); |
| out.startTag(null, "cert"); |
| out.attributeInt(null, "index", 10); |
| out.attributeBytesHex(null, "key", KEY_BLOB); |
| out.endTag(null, "cert"); |
| out.endTag(null, "sigs"); |
| |
| out.startTag(null, "perms"); |
| for (String perm : PERMS) { |
| out.startTag(null, "item"); |
| out.attributeInterned(null, "name", perm); |
| out.attributeBoolean(null, "granted", true); |
| out.attributeInt(null, "flags", 0); |
| out.endTag(null, "item"); |
| } |
| out.endTag(null, "perms"); |
| |
| out.endTag(null, "package"); |
| } |
| out.endTag(null, "packages"); |
| out.endDocument(); |
| } |
| |
| /** |
| * Read a typical {@code packages.xml} file containing 100 applications, and |
| * verify that data passes smell test. |
| */ |
| private static void read(TypedXmlPullParser xml) throws Exception { |
| int type; |
| int packages = 0; |
| int certs = 0; |
| int perms = 0; |
| while ((type = xml.next()) != XmlPullParser.END_DOCUMENT) { |
| final String tag = xml.getName(); |
| if (type == XmlPullParser.START_TAG) { |
| if ("package".equals(tag)) { |
| xml.getAttributeValue(null, "name"); |
| xml.getAttributeValue(null, "codePath"); |
| xml.getAttributeValue(null, "nativeLibraryPath"); |
| xml.getAttributeLong(null, "publicFlags"); |
| assertEquals(-1946152952L, xml.getAttributeLong(null, "privateFlags")); |
| xml.getAttributeLong(null, "ft"); |
| xml.getAttributeLong(null, "it"); |
| xml.getAttributeLong(null, "ut"); |
| xml.getAttributeInt(null, "version"); |
| xml.getAttributeInt(null, "sharedUserId"); |
| xml.getAttributeBoolean(null, "isOrphaned"); |
| packages++; |
| } else if ("cert".equals(tag)) { |
| xml.getAttributeInt(null, "index"); |
| xml.getAttributeBytesHex(null, "key"); |
| certs++; |
| } else if ("item".equals(tag)) { |
| xml.getAttributeValue(null, "name"); |
| xml.getAttributeBoolean(null, "granted"); |
| xml.getAttributeInt(null, "flags"); |
| perms++; |
| } |
| } else if (type == XmlPullParser.TEXT) { |
| xml.getText(); |
| } |
| } |
| |
| assertEquals(100, packages); |
| assertEquals(packages * 1, certs); |
| assertEquals(packages * PERMS.length, perms); |
| } |
| } |