• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 package com.android.compos.benchmark;
17 
18 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
19 
20 import static com.google.common.truth.Truth.assertThat;
21 import static com.google.common.truth.TruthJUnit.assume;
22 
23 import static org.junit.Assert.assertNotNull;
24 import static org.junit.Assert.assertTrue;
25 
26 import android.app.Instrumentation;
27 import android.os.Bundle;
28 import android.util.Log;
29 
30 import com.android.microdroid.test.common.MetricsProcessor;
31 import com.android.microdroid.test.common.ProcessUtil;
32 import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
33 
34 import org.junit.After;
35 import org.junit.Before;
36 import org.junit.Test;
37 import org.junit.runner.RunWith;
38 import org.junit.runners.JUnit4;
39 
40 import java.io.IOException;
41 import java.sql.Timestamp;
42 import java.text.DateFormat;
43 import java.text.ParseException;
44 import java.text.SimpleDateFormat;
45 import java.util.ArrayList;
46 import java.util.Date;
47 import java.util.HashMap;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.concurrent.atomic.AtomicBoolean;
51 import java.util.regex.Matcher;
52 import java.util.regex.Pattern;
53 
54 @RunWith(JUnit4.class)
55 public class ComposBenchmark extends MicrodroidDeviceTestBase {
56     private static final String TAG = "ComposBenchmark";
57     private static final int BUFFER_SIZE = 1024;
58     private static final int ROUND_COUNT = 5;
59     private static final double NANOS_IN_SEC = 1_000_000_000.0;
60     private static final String METRIC_PREFIX = getMetricPrefix() + "compos/";
61 
62     private final MetricsProcessor mMetricsProcessor = new MetricsProcessor(METRIC_PREFIX);
63 
64     private Instrumentation mInstrumentation;
65 
66     @Before
setup()67     public void setup() {
68         mInstrumentation = getInstrumentation();
69         mInstrumentation.getUiAutomation().adoptShellPermissionIdentity();
70     }
71 
72     @After
tearDown()73     public void tearDown() {
74         mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
75     }
76 
77     @Test
testHostCompileTime()78     public void testHostCompileTime() throws Exception {
79         final String command = "/apex/com.android.art/bin/odrefresh --force-compile";
80 
81         final List<Double> compileTimes = new ArrayList<>(ROUND_COUNT);
82         // The mapping is <memory metrics name> -> <all rounds value list>.
83         // EX : pss -> [10, 20, 30, ........]
84         final Map<String, List<Long>> processMemory = new HashMap<>();
85 
86         for (int round = 0; round < ROUND_COUNT; ++round) {
87 
88             GetMetricsRunnable getMetricsRunnable =
89                     new GetMetricsRunnable("dex2oat64", processMemory);
90             Thread threadGetMetrics = new Thread(getMetricsRunnable);
91 
92             threadGetMetrics.start();
93 
94             Timestamp beforeCompileLatestTime = getLatestDex2oatSuccessTime();
95             Long compileStartTime = System.nanoTime();
96             executeCommand(command);
97             Long compileEndTime = System.nanoTime();
98             Timestamp afterCompileLatestTime = getLatestDex2oatSuccessTime();
99 
100             assertNotNull(afterCompileLatestTime);
101             assertTrue(
102                     beforeCompileLatestTime == null
103                             || beforeCompileLatestTime.before(afterCompileLatestTime));
104 
105             double elapsedSec = (compileEndTime - compileStartTime) / NANOS_IN_SEC;
106             Log.i(TAG, "Compile time in host took " + elapsedSec + "s");
107             getMetricsRunnable.stop();
108 
109             Log.i(TAG, "Waits for thread finish");
110             threadGetMetrics.join();
111             Log.i(TAG, "Thread is finish");
112 
113             compileTimes.add(elapsedSec);
114         }
115 
116         reportMetric("host_compile_time", "s", compileTimes);
117 
118         reportAggregatedMetric(processMemory, "host_compile_dex2oat64_", "kB");
119     }
120 
121     @Test
testGuestCompileTime()122     public void testGuestCompileTime() throws Exception {
123         assume().withMessage("Skip on CF; too slow").that(isCuttlefish()).isFalse();
124         final String command = "/apex/com.android.compos/bin/composd_cmd test-compile";
125 
126         final List<Double> compileTimes = new ArrayList<>(ROUND_COUNT);
127         // The mapping is <memory metrics name> -> <all rounds value list>.
128         // EX : pss -> [10, 20, 30, ........]
129         final Map<String, List<Long>> processMemory = new HashMap<>();
130 
131         for (int round = 0; round < ROUND_COUNT; ++round) {
132 
133             GetMetricsRunnable getMetricsRunnable = new GetMetricsRunnable("crosvm", processMemory);
134             Thread threadGetMetrics = new Thread(getMetricsRunnable);
135 
136             threadGetMetrics.start();
137 
138             Long compileStartTime = System.nanoTime();
139             String output = runInShellWithStderr(TAG, mInstrumentation.getUiAutomation(), command);
140             Long compileEndTime = System.nanoTime();
141             assertThat(output).containsMatch("All Ok");
142             double elapsedSec = (compileEndTime - compileStartTime) / NANOS_IN_SEC;
143             Log.i(TAG, "Compile time in guest took " + elapsedSec + "s");
144             getMetricsRunnable.stop();
145 
146             Log.i(TAG, "Waits for thread finish");
147             threadGetMetrics.join();
148             Log.i(TAG, "Thread is finish");
149 
150             compileTimes.add(elapsedSec);
151         }
152 
153         reportMetric("guest_compile_time", "s", compileTimes);
154 
155         reportAggregatedMetric(processMemory, "guest_compile_crosvm_", "kB");
156     }
157 
getLatestDex2oatSuccessTime()158     private Timestamp getLatestDex2oatSuccessTime()
159             throws InterruptedException, IOException, ParseException {
160         final String command = "logcat -d -e dex2oat";
161         String output = executeCommand(command);
162         String latestTime = null;
163 
164         for (String line : output.split("[\r\n]+")) {
165             Pattern pattern = Pattern.compile("dex2oat64: dex2oat took");
166             Matcher matcher = pattern.matcher(line);
167             if (matcher.find()) {
168                 latestTime = line.substring(0, 18);
169             }
170         }
171 
172         if (latestTime == null) {
173             return null;
174         }
175 
176         DateFormat formatter = new SimpleDateFormat("MM-dd hh:mm:ss.SSS");
177         Date date = formatter.parse(latestTime);
178         Timestamp timeStampDate = new Timestamp(date.getTime());
179 
180         return timeStampDate;
181     }
182 
reportMetric(String name, String unit, List<? extends Number> values)183     private void reportMetric(String name, String unit, List<? extends Number> values) {
184         Log.d(TAG, "Report metric " + name + "(" + unit + ") : " + values.toString());
185         Map<String, Double> stats = mMetricsProcessor.computeStats(values, name, unit);
186         Bundle bundle = new Bundle();
187         for (Map.Entry<String, Double> entry : stats.entrySet()) {
188             bundle.putDouble(entry.getKey(), entry.getValue());
189         }
190         mInstrumentation.sendStatus(0, bundle);
191     }
192 
reportAggregatedMetric( Map<String, List<Long>> processMemory, String prefix, String unit)193     private void reportAggregatedMetric(
194             Map<String, List<Long>> processMemory, String prefix, String unit) {
195         processMemory.forEach((k, v) -> reportMetric(prefix + k, unit, v));
196     }
197 
executeCommand(String command)198     private String executeCommand(String command) {
199         return runInShell(TAG, mInstrumentation.getUiAutomation(), command);
200     }
201 
202     private class GetMetricsRunnable implements Runnable {
203         private final String mProcessName;
204         private Map<String, List<Long>> mProcessMemory;
205         private AtomicBoolean mStop = new AtomicBoolean(false);
206 
GetMetricsRunnable(String processName, Map<String, List<Long>> processMemory)207         GetMetricsRunnable(String processName, Map<String, List<Long>> processMemory) {
208             this.mProcessName = processName;
209             this.mProcessMemory = processMemory;
210         }
211 
stop()212         void stop() {
213             mStop.set(true);
214         }
215 
run()216         public void run() {
217             while (!mStop.get()) {
218                 try {
219                     updateProcessMemory(mProcessName, mProcessMemory);
220                     Thread.sleep(1000);
221                 } catch (InterruptedException e) {
222                     Thread.currentThread().interrupt();
223                     return;
224                 } catch (Exception e) {
225                     Log.e(TAG, "Get exception : " + e);
226                     throw new RuntimeException(e);
227                 }
228             }
229         }
230     }
231 
updateProcessMemory(String processName, Map<String, List<Long>> processMemory)232     private void updateProcessMemory(String processName, Map<String, List<Long>> processMemory)
233             throws Exception {
234         for (Map.Entry<Integer, String> process :
235                 ProcessUtil.getProcessMap(this::executeCommand).entrySet()) {
236             int pId = process.getKey();
237             String pName = process.getValue();
238             if (pName.equalsIgnoreCase(processName)) {
239                 for (Map.Entry<String, Long> stat :
240                         ProcessUtil.getProcessSmapsRollup(pId, this::executeCommand).entrySet()) {
241                     Log.i(
242                             TAG,
243                             "Get running process "
244                                     + pName
245                                     + " metrics : "
246                                     + stat.getKey().toLowerCase()
247                                     + '-'
248                                     + stat.getValue());
249                     processMemory
250                             .computeIfAbsent(stat.getKey().toLowerCase(), k -> new ArrayList<>())
251                             .add(stat.getValue());
252                 }
253             }
254         }
255     }
256 }
257