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