1 /*
2  * 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.graphics.perftests;
18 
19 import android.graphics.Typeface;
20 import android.os.Debug;
21 import android.os.SharedMemory;
22 import android.os.SystemClock;
23 import android.perftests.utils.ManualBenchmarkState;
24 import android.perftests.utils.PerfManualStatusReporter;
25 import android.util.ArrayMap;
26 import android.util.Log;
27 
28 import androidx.test.filters.LargeTest;
29 import androidx.test.runner.AndroidJUnit4;
30 
31 import org.junit.Before;
32 import org.junit.Rule;
33 import org.junit.Test;
34 import org.junit.runner.RunWith;
35 
36 import java.nio.ByteBuffer;
37 import java.nio.ByteOrder;
38 import java.util.Map;
39 
40 @LargeTest
41 @RunWith(AndroidJUnit4.class)
42 public class TypefaceSerializationPerfTest {
43 
44     private static final String TAG = "TypefaceSerializationPerfTest";
45 
46     // Those values were taken from android.perftests.utils.BenchmarkState.
47     // Note: we cannot use TimeUnit.toNanos() because these constants are used in annotation.
48     private static final long WARMUP_DURATION_NS = 250_000_000; // 250ms
49     private static final long TARGET_TEST_DURATION_NS = 500_000_000; // 500ms
50 
51     @Rule
52     public PerfManualStatusReporter mPerfManualStatusReporter = new PerfManualStatusReporter();
53 
54     @Before
setUp()55     public void setUp() {
56         // Parse and load the preinstalled fonts in the test process so that:
57         // (1) Updated fonts do not affect test results.
58         // (2) Lazy-loading of fonts does not affect test results (esp. testSerializeFontMap).
59         Typeface.loadPreinstalledSystemFontMap();
60     }
61 
62     // testSerializeFontMap uses the default targetTestDurationNs, which is much longer than
63     // TARGET_TEST_DURATION_NS, in order to stabilize test results.
64     @Test
testSerializeFontMap()65     public void testSerializeFontMap() throws Exception {
66         Map<String, Typeface> systemFontMap = Typeface.getSystemFontMap();
67         ManualBenchmarkState state = mPerfManualStatusReporter.getBenchmarkState();
68 
69         long elapsedTime = 0;
70         while (state.keepRunning(elapsedTime)) {
71             long startTime = System.nanoTime();
72             SharedMemory sharedMemory = Typeface.serializeFontMap(systemFontMap);
73             elapsedTime = System.nanoTime() - startTime;
74             sharedMemory.close();
75             android.util.Log.i(TAG,
76                     "testSerializeFontMap isWarmingUp=" + state.isWarmingUp()
77                             + " elapsedTime=" + elapsedTime);
78         }
79     }
80 
81     @ManualBenchmarkState.ManualBenchmarkTest(
82             warmupDurationNs = WARMUP_DURATION_NS,
83             targetTestDurationNs = TARGET_TEST_DURATION_NS)
84     @Test
testSerializeFontMap_memory()85     public void testSerializeFontMap_memory() throws Exception {
86         Map<String, Typeface> systemFontMap = Typeface.getSystemFontMap();
87         SharedMemory memory = Typeface.serializeFontMap(systemFontMap);
88         ManualBenchmarkState state = mPerfManualStatusReporter.getBenchmarkState();
89 
90         while (state.keepRunning(memory.getSize())) {
91             // Rate-limiting
92             SystemClock.sleep(100);
93         }
94     }
95 
96     @ManualBenchmarkState.ManualBenchmarkTest(
97             warmupDurationNs = WARMUP_DURATION_NS,
98             targetTestDurationNs = TARGET_TEST_DURATION_NS)
99     @Test
testDeserializeFontMap()100     public void testDeserializeFontMap() throws Exception {
101         SharedMemory memory = Typeface.serializeFontMap(Typeface.getSystemFontMap());
102         ByteBuffer buffer = memory.mapReadOnly().order(ByteOrder.BIG_ENDIAN);
103         ManualBenchmarkState state = mPerfManualStatusReporter.getBenchmarkState();
104 
105         ArrayMap<String, Typeface> out = new ArrayMap<>();
106         long elapsedTime = 0;
107         while (state.keepRunning(elapsedTime)) {
108             long startTime = System.nanoTime();
109             buffer.position(0);
110             Typeface.deserializeFontMap(buffer, out);
111             elapsedTime = System.nanoTime() - startTime;
112             for (Typeface typeface : out.values()) {
113                 typeface.releaseNativeObjectForTest();
114             }
115             out.clear();
116         }
117     }
118 
119     @ManualBenchmarkState.ManualBenchmarkTest(
120             warmupDurationNs = WARMUP_DURATION_NS,
121             targetTestDurationNs = TARGET_TEST_DURATION_NS)
122     @Test
testDeserializeFontMap_memory()123     public void testDeserializeFontMap_memory() throws Exception {
124         SharedMemory memory = Typeface.serializeFontMap(Typeface.getSystemFontMap());
125         ByteBuffer buffer = memory.mapReadOnly().order(ByteOrder.BIG_ENDIAN);
126         ManualBenchmarkState state = mPerfManualStatusReporter.getBenchmarkState();
127 
128         ArrayMap<String, Typeface> out = new ArrayMap<>();
129         // Diff of native heap allocation size (in bytes) before and after deserializeFontMap.
130         // Note: we don't measure memory usage of setSystemFontMap because setSystemFontMap sets
131         // some global variables, and it's hard to clear them.
132         long heapDiff = 0;
133         // Sometimes heapDiff may become negative due to GC.
134         // Use 0 in that case to avoid crashing in keepRunning.
135         while (state.keepRunning(Math.max(0, heapDiff))) {
136             buffer.position(0);
137             long baselineSize = Debug.getNativeHeapAllocatedSize();
138             Typeface.deserializeFontMap(buffer, out);
139             long currentSize = Debug.getNativeHeapAllocatedSize();
140             heapDiff = currentSize - baselineSize;
141             Log.i(TAG, String.format("native heap alloc: current = %d, baseline = %d, diff = %d",
142                     currentSize, baselineSize, heapDiff));
143             // Release native objects here to minimize the impact of GC.
144             for (Typeface typeface : out.values()) {
145                 typeface.releaseNativeObjectForTest();
146             }
147             out.clear();
148         }
149     }
150 }
151