1 /*
2  * Copyright (C) 2022 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.content.res;
18 
19 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
20 import static org.xmlpull.v1.XmlPullParser.END_TAG;
21 import static org.xmlpull.v1.XmlPullParser.START_DOCUMENT;
22 import static org.xmlpull.v1.XmlPullParser.START_TAG;
23 
24 import android.content.Context;
25 import android.perftests.utils.BenchmarkState;
26 import android.perftests.utils.PerfStatusReporter;
27 import android.text.TextUtils;
28 import android.util.Log;
29 
30 import androidx.test.filters.LargeTest;
31 import androidx.test.platform.app.InstrumentationRegistry;
32 
33 import com.android.perftests.core.R;
34 
35 import org.junit.After;
36 import org.junit.Assert;
37 import org.junit.Before;
38 import org.junit.Rule;
39 import org.junit.Test;
40 import org.xmlpull.v1.XmlPullParserException;
41 
42 import java.io.IOException;
43 import java.util.function.Function;
44 import java.util.function.Supplier;
45 
46 @LargeTest
47 public class XmlBlockBenchmark {
48     private static final String TAG = "XmlBlockBenchmark";
49     private static final String NAMESPACE_ANDROID = "http://schemas.android.com/apk/res/android";
50 
51     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
52     private XmlBlock.Parser mParser;
53 
cleanCache()54     private void cleanCache() {
55         if (mParser != null) {
56             mParser.close();
57         }
58 
59         final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
60         final Resources resources = context.getResources();
61         resources.getImpl().clearAllCaches();
62         Log.d(TAG, "cleanCache");
63     }
64 
getNewParser()65     private XmlBlock.Parser getNewParser() {
66         cleanCache();
67 
68         final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
69         final Resources resources = context.getResources();
70         return (XmlBlock.Parser) resources.getXml(R.layout.linear_layout_for_xmlblock_benchmark);
71     }
72 
73     @Before
setUp()74     public void setUp() {
75         mParser = getNewParser();
76     }
77 
78     @After
tearDown()79     public void tearDown() {
80         cleanCache();
81     }
82 
safeNext()83     int safeNext() throws XmlPullParserException, IOException {
84         while (true) {
85             int parseState = mParser.next();
86             if (parseState == START_TAG) {
87                 return parseState;
88             } else if (parseState == END_DOCUMENT) {
89                 mParser = getNewParser();
90             }
91         }
92     }
93 
94     @Test
throwNpeCausedByNullDocument()95     public void throwNpeCausedByNullDocument() {
96         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
97         mParser.close();
98         while (state.keepRunning()) {
99             try {
100                 mParser.getClassAttribute();
101             } catch (NullPointerException e) {
102                 continue;
103             }
104             Assert.fail("It shouldn't be here!");
105         }
106     }
107 
108     @Test
getNext()109     public void getNext() throws XmlPullParserException, IOException {
110         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
111         while (state.keepRunning()) {
112             int parseState = mParser.next();
113             state.pauseTiming();
114             if (parseState == END_DOCUMENT) {
115                 mParser = getNewParser();
116             }
117             state.resumeTiming();
118         }
119     }
120 
benchmarkTagFunction(BenchmarkState state, String name, Supplier<T> measureTarget)121     private <T> void benchmarkTagFunction(BenchmarkState state, String name,
122             Supplier<T> measureTarget)
123             throws XmlPullParserException, IOException {
124         while (state.keepRunning()) {
125             state.pauseTiming();
126             int parseState = safeNext();
127 
128             if (parseState != END_DOCUMENT) {
129                 final String tagName = mParser.getName();
130                 state.resumeTiming();
131                 final T value = measureTarget.get();
132                 state.pauseTiming();
133                 Log.d(TAG,
134                         TextUtils.formatSimple("%s() in tag %s is %s", name, tagName, value));
135             }
136             state.resumeTiming();
137         }
138     }
139 
140     @Test
getNamespace()141     public void getNamespace() throws XmlPullParserException, IOException {
142         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
143         benchmarkTagFunction(state, "getNamespace", () -> mParser.getNamespace());
144     }
145 
146     @Test
getName()147     public void getName() throws XmlPullParserException, IOException {
148         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
149         benchmarkTagFunction(state, "getName", () -> mParser.getName());
150     }
151 
152     @Test
getText()153     public void getText() throws XmlPullParserException, IOException {
154         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
155         benchmarkTagFunction(state, "getText", () -> mParser.getText());
156     }
157 
158     @Test
getLineNumber()159     public void getLineNumber() throws XmlPullParserException, IOException {
160         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
161         benchmarkTagFunction(state, "getLineNumber", () -> mParser.getLineNumber());
162     }
163 
164     @Test
getAttributeCount()165     public void getAttributeCount() throws XmlPullParserException, IOException {
166         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
167         benchmarkTagFunction(state, "getAttributeCount", () -> mParser.getAttributeCount());
168     }
169 
benchmarkAttributeFunction(BenchmarkState state, String name, Function<Integer, T> measureTarget)170     private <T> void benchmarkAttributeFunction(BenchmarkState state, String name,
171             Function<Integer, T> measureTarget)
172             throws XmlPullParserException, IOException {
173         boolean needNext = true;
174         boolean needGetCount = false;
175         int attributeCount = 0;
176         int i = 0;
177         while (state.keepRunning()) {
178             state.pauseTiming();
179             if (needNext) {
180                 int parseState = safeNext();
181                 if (parseState == START_TAG) {
182                     needNext = false;
183                     needGetCount = true;
184                 }
185             }
186 
187             if (needGetCount) {
188                 attributeCount = mParser.getAttributeCount();
189                 needGetCount = false;
190                 i = 0;
191             }
192 
193             if (i < attributeCount) {
194                 final String tagName = mParser.getName();
195                 final String attributeName = mParser.getAttributeName(i);
196                 state.resumeTiming();
197                 final T value = measureTarget.apply(i);
198                 state.pauseTiming();
199                 Log.d(TAG,
200                         TextUtils.formatSimple("%s(%d:%s) in tag %s is %s", name, i, attributeName,
201                                 tagName, value));
202                 i++;
203             }
204 
205             if (i >= attributeCount) {
206                 needNext = true;
207             }
208             state.resumeTiming();
209         }
210     }
211 
212     @Test
getAttributeNamespace()213     public void getAttributeNamespace() throws XmlPullParserException, IOException {
214         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
215         benchmarkAttributeFunction(state, "getAttributeNamespace",
216                 attributeIndex -> mParser.getAttributeNamespace(attributeIndex));
217     }
218 
219     @Test
getAttributeName()220     public void getAttributeName() throws XmlPullParserException, IOException {
221         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
222         benchmarkAttributeFunction(state, "getAttributeName",
223                 attributeIndex -> mParser.getAttributeName(attributeIndex));
224     }
225 
226     @Test
getAttributeNameResource()227     public void getAttributeNameResource() throws XmlPullParserException, IOException {
228         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
229         benchmarkAttributeFunction(state, "getAttributeNameResource",
230                 attributeIndex -> mParser.getAttributeNameResource(attributeIndex));
231     }
232 
233     /**
234      * benchmark {@link android.content.res.XmlBlock#nativeGetAttributeDataType(long, int)} and
235      * {@link android.content.res.XmlBlock#nativeGetAttributeData(long, int)}
236      */
237     @Test
getAttributeDataXXX()238     public void getAttributeDataXXX() throws XmlPullParserException, IOException {
239         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
240         benchmarkAttributeFunction(state, "getAttributeDataXXX",
241                 attributeIndex -> mParser.getAttributeValue(attributeIndex));
242     }
243 
244     @Test
getSourceResId()245     public void getSourceResId() throws XmlPullParserException, IOException {
246         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
247         benchmarkTagFunction(state, "getSourceResId", () -> mParser.getSourceResId());
248     }
249 
250     @Test
getIdAttribute()251     public void getIdAttribute() throws XmlPullParserException, IOException {
252         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
253         benchmarkTagFunction(state, "getIdAttribute", () -> mParser.getIdAttribute());
254     }
255 
256     @Test
getClassAttribute()257     public void getClassAttribute() throws XmlPullParserException, IOException {
258         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
259         benchmarkTagFunction(state, "getClassAttribute", () -> mParser.getClassAttribute());
260     }
261 
262     @Test
getStyleAttribute()263     public void getStyleAttribute() throws XmlPullParserException, IOException {
264         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
265         benchmarkTagFunction(state, "getStyleAttribute", () -> mParser.getStyleAttribute());
266     }
267 
268     @Test
getAttributeIndex()269     public void getAttributeIndex() throws XmlPullParserException, IOException {
270         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
271         benchmarkTagFunction(state, "getAttributeValue",
272                 () -> mParser.getAttributeValue(NAMESPACE_ANDROID, "layout_width"));
273     }
274 
275     @Test
parseOneXmlDocument()276     public void parseOneXmlDocument() throws XmlPullParserException, IOException {
277         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
278         while (state.keepRunning()) {
279             state.pauseTiming();
280             mParser = getNewParser();
281             state.resumeTiming();
282 
283             int parseState;
284             while ((parseState = mParser.next()) != END_DOCUMENT) {
285                 if (parseState == START_DOCUMENT) {
286                     state.pauseTiming();
287                     Log.d(TAG, "parseOneXmlDocument: start document");
288                     state.resumeTiming();
289                 } else if (parseState == START_TAG) {
290                     final String tagName = mParser.getName();
291                     state.pauseTiming();
292                     Log.d(TAG, TextUtils.formatSimple("parseOneXmlDocument: tag %s {[", tagName));
293                     state.resumeTiming();
294                     for (int i = 0, count = mParser.getAttributeCount(); i < count; i++) {
295                         final String attributeName = mParser.getAttributeName(i);
296                         final String attributeValue = mParser.getAttributeValue(i);
297 
298                         state.pauseTiming();
299                         Log.d(TAG, TextUtils.formatSimple(
300                                 "parseOneXmlDocument: attribute %d {%s = %s},", i, attributeName,
301                                 attributeValue));
302                         state.resumeTiming();
303                     }
304                     state.pauseTiming();
305                     Log.d(TAG, "parseOneXmlDocument: ]");
306                     state.resumeTiming();
307                 } else if (parseState == END_TAG) {
308                     state.pauseTiming();
309                     Log.d(TAG, "parseOneXmlDocument: }");
310                     state.resumeTiming();
311                 } else {
312                     final String text = mParser.getText();
313                     state.pauseTiming();
314                     Log.d(TAG, TextUtils.formatSimple(
315                             "parseOneXmlDocument: parseState = %d, text = %s", parseState, text));
316                     state.resumeTiming();
317                 }
318             }
319         }
320     }
321 }
322