1 /* 2 * Copyright (C) 2013 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.tests.applaunch; 17 18 import static org.junit.Assert.assertNotNull; 19 20 import android.accounts.Account; 21 import android.accounts.AccountManager; 22 import android.app.ActivityManager; 23 import android.app.ActivityManager.ProcessErrorStateInfo; 24 import android.app.IActivityManager; 25 import android.app.UiAutomation; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.pm.PackageManager; 29 import android.content.pm.PackageManager.NameNotFoundException; 30 import android.content.pm.ResolveInfo; 31 import android.os.Bundle; 32 import android.os.ParcelFileDescriptor; 33 import android.os.RemoteException; 34 import android.os.SystemClock; 35 import android.os.UserHandle; 36 import android.support.test.uiautomator.UiDevice; 37 import android.test.InstrumentationTestCase; 38 import android.test.InstrumentationTestRunner; 39 import android.util.Log; 40 41 import androidx.test.rule.logging.AtraceLogger; 42 43 import java.io.BufferedReader; 44 import java.io.BufferedWriter; 45 import java.io.File; 46 import java.io.FileInputStream; 47 import java.io.FileOutputStream; 48 import java.io.FileWriter; 49 import java.io.IOException; 50 import java.io.InputStream; 51 import java.io.InputStreamReader; 52 import java.io.OutputStreamWriter; 53 import java.nio.file.Paths; 54 import java.time.format.DateTimeFormatter; 55 import java.time.ZonedDateTime; 56 import java.time.ZoneOffset; 57 import java.util.ArrayList; 58 import java.util.Arrays; 59 import java.util.HashMap; 60 import java.util.HashSet; 61 import java.util.LinkedHashMap; 62 import java.util.List; 63 import java.util.Map; 64 import java.util.Set; 65 66 /** 67 * This test is intended to measure the time it takes for the apps to start. 68 * Names of the applications are passed in command line, and the 69 * test starts each application, and reports the start up time in milliseconds. 70 * The instrumentation expects the following key to be passed on the command line: 71 * apps - A list of applications to start and their corresponding result keys 72 * in the following format: 73 * -e apps <app name>^<result key>|<app name>^<result key> 74 */ 75 @Deprecated 76 public class AppLaunch extends InstrumentationTestCase { 77 78 private static final int JOIN_TIMEOUT = 10000; 79 private static final String TAG = AppLaunch.class.getSimpleName(); 80 81 // optional parameter: comma separated list of required account types before proceeding 82 // with the app launch 83 private static final String KEY_REQUIRED_ACCOUNTS = "required_accounts"; 84 private static final String KEY_APPS = "apps"; 85 private static final String KEY_IORAP_TRIAL_LAUNCH = "iorap_trial_launch"; 86 private static final String KEY_IORAP_COMPILER_FILTERS = "iorap_compiler_filters"; 87 private static final String KEY_TRIAL_LAUNCH = "trial_launch"; 88 private static final String KEY_LAUNCH_ITERATIONS = "launch_iterations"; 89 private static final String KEY_LAUNCH_ORDER = "launch_order"; 90 private static final String KEY_DROP_CACHE = "drop_cache"; 91 private static final String KEY_SIMPLEPERF_CMD = "simpleperf_cmd"; 92 private static final String KEY_SIMPLEPERF_APP = "simpleperf_app"; 93 private static final String KEY_CYCLE_CLEAN = "cycle_clean"; 94 private static final String KEY_TRACE_ALL = "trace_all"; 95 private static final String KEY_TRACE_ITERATIONS = "trace_iterations"; 96 private static final String KEY_LAUNCH_DIRECTORY = "launch_directory"; 97 private static final String KEY_TRACE_DIRECTORY = "trace_directory"; 98 private static final String KEY_TRACE_CATEGORY = "trace_categories"; 99 private static final String KEY_TRACE_BUFFERSIZE = "trace_bufferSize"; 100 private static final String KEY_TRACE_DUMPINTERVAL = "tracedump_interval"; 101 private static final String KEY_COMPILER_FILTERS = "compiler_filters"; 102 private static final String KEY_FORCE_STOP_APP = "force_stop_app"; 103 private static final String ENABLE_SCREEN_RECORDING = "enable_screen_recording"; 104 private static final int MAX_RECORDING_PARTS = 5; 105 private static final long VIDEO_TAIL_BUFFER = 500; 106 107 private static final String SIMPLEPERF_APP_CMD = 108 "simpleperf --log fatal stat --csv -e cpu-cycles,major-faults --app %s & %s"; 109 private static final String WEARABLE_ACTION_GOOGLE = 110 "com.google.android.wearable.action.GOOGLE"; 111 private static final int INITIAL_LAUNCH_IDLE_TIMEOUT = 5000; // 5s to allow app to idle 112 private static final int POST_LAUNCH_IDLE_TIMEOUT = 750; // 750ms idle for non initial launches 113 private static final int BEFORE_FORCE_STOP_SLEEP_TIMEOUT = 1000; // 1s before force stopping 114 private static final int BEFORE_KILL_APP_SLEEP_TIMEOUT = 1000; // 1s before killing 115 private static final int BETWEEN_LAUNCH_SLEEP_TIMEOUT = 3000; // 3s between launching apps 116 private static final int PROFILE_SAVE_SLEEP_TIMEOUT = 1000; // Allow 1s for the profile to save 117 private static final int IORAP_TRACE_DURATION_TIMEOUT = 7000; // Allow 7s for trace to complete. 118 private static final int IORAP_TRIAL_LAUNCH_ITERATIONS = 3; // min 3 launches to merge traces. 119 private static final int IORAP_COMPILE_CMD_TIMEOUT = 60; // in seconds: 1 minutes 120 private static final int IORAP_COMPILE_MIN_TRACES = 1; // configure iorapd to need 1 trace. 121 private static final int IORAP_COMPILE_RETRIES = 3; // retry compiler 3 times if it fails. 122 private static final String LAUNCH_SUB_DIRECTORY = "launch_logs"; 123 private static final String LAUNCH_FILE = "applaunch.txt"; 124 private static final String TRACE_SUB_DIRECTORY = "atrace_logs"; 125 private static final String DEFAULT_TRACE_CATEGORIES = 126 "sched,freq,gfx,view,dalvik,webview,input,wm,disk,am,wm,binder_driver,hal,ss"; 127 private static final String DEFAULT_TRACE_BUFFER_SIZE = "20000"; 128 private static final String DEFAULT_TRACE_DUMP_INTERVAL = "10"; 129 private static final String TRIAL_LAUNCH = "TRIAL_LAUNCH"; 130 private static final String IORAP_TRIAL_LAUNCH = "IORAP_TRIAL_LAUNCH"; 131 private static final String IORAP_TRIAL_LAUNCH_FIRST = "IORAP_TRIAL_LAUNCH_FIRST"; 132 private static final String IORAP_TRIAL_LAUNCH_LAST = "IORAP_TRIAL_LAUNCH_LAST"; 133 private static final String DELIMITER = ","; 134 private static final String DROP_CACHE_SCRIPT = "/data/local/tmp/dropCache.sh"; 135 private static final String APP_LAUNCH_CMD = "am start -W -n"; 136 private static final String SUCCESS_MESSAGE = "Status: ok"; 137 private static final String TOTAL_TIME_MESSAGE = "TotalTime:"; 138 private static final String COMPILE_SUCCESS = "Success"; 139 private static final String LAUNCH_ITERATION = "LAUNCH_ITERATION-%d"; 140 private static final String TRACE_ITERATION = "TRACE_ITERATION-%d"; 141 private static final String LAUNCH_ITERATION_PREFIX = "LAUNCH_ITERATION"; 142 private static final String TRACE_ITERATION_PREFIX = "TRACE_ITERATION"; 143 private static final String LAUNCH_ORDER_CYCLIC = "cyclic"; 144 private static final String LAUNCH_ORDER_SEQUENTIAL = "sequential"; 145 private static final String COMPILE_CMD = "cmd package compile -f -m %s %s"; 146 private static final String IORAP_COMPILE_CMD = "dumpsys iorapd --compile-package %s"; 147 private static final String IORAP_MAINTENANCE_CMD = 148 "dumpsys iorapd --purge-package %s"; 149 private static final String IORAP_DUMPSYS_CMD = "dumpsys iorapd"; 150 private static final String SPEED_PROFILE_FILTER = "speed-profile"; 151 private static final String VERIFY_FILTER = "verify"; 152 private static final String LAUNCH_SCRIPT_NAME = "appLaunch"; 153 154 private Map<String, Intent> mNameToIntent; 155 private List<LaunchOrder> mLaunchOrderList = new ArrayList<LaunchOrder>(); 156 private RecordingThread mCurrentThread; 157 private Map<String, String> mNameToResultKey; 158 private Map<String, Map<String, List<AppLaunchResult>>> mNameToLaunchTime; 159 private IActivityManager mAm; 160 private File launchSubDir = null; 161 private String mSimplePerfCmd = null; 162 private String mLaunchOrder = null; 163 private boolean mDropCache = false; 164 private int mLaunchIterations = 10; 165 private boolean mForceStopApp = true; 166 private boolean mEnableRecording = false; 167 private int mTraceLaunchCount = 0; 168 private String mTraceDirectoryStr = null; 169 private Bundle mResult = new Bundle(); 170 private Set<String> mRequiredAccounts; 171 private boolean mTrialLaunch = false; 172 private boolean mIorapTrialLaunch = false; 173 private BufferedWriter mBufferedWriter = null; 174 private boolean mSimplePerfAppOnly = false; 175 private String[] mCompilerFilters = null; 176 private List<String> mIorapCompilerFilters = null; 177 private String mLastAppName = ""; 178 private boolean mCycleCleanUp = false; 179 private boolean mTraceAll = false; 180 private boolean mIterationCycle = false; 181 private UiDevice mDevice; 182 183 enum IorapStatus { 184 UNDEFINED, 185 ENABLED, 186 DISABLED 187 } 188 private IorapStatus mIorapStatus = IorapStatus.UNDEFINED; 189 private long mCycleTime = 0; 190 private StringBuilder mCycleTimes = new StringBuilder(); 191 192 @Override setUp()193 protected void setUp() throws Exception { 194 super.setUp(); 195 getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_0); 196 } 197 198 @Override tearDown()199 protected void tearDown() throws Exception { 200 getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_UNFREEZE); 201 super.tearDown(); 202 } 203 addLaunchResult(LaunchOrder launch, AppLaunchResult result)204 private void addLaunchResult(LaunchOrder launch, AppLaunchResult result) { 205 mNameToLaunchTime.get(launch.getApp()).get(launch.getCompilerFilter()).add(result); 206 } 207 hasFailureOnFirstLaunch(LaunchOrder launch)208 private boolean hasFailureOnFirstLaunch(LaunchOrder launch) { 209 List<AppLaunchResult> results = 210 mNameToLaunchTime.get(launch.getApp()).get(launch.getCompilerFilter()); 211 return (results.size() > 0) && (results.get(0).mLaunchTime < 0); 212 } 213 testMeasureStartUpTime()214 public void testMeasureStartUpTime() throws RemoteException, NameNotFoundException, 215 IOException, InterruptedException { 216 InstrumentationTestRunner instrumentation = 217 (InstrumentationTestRunner)getInstrumentation(); 218 Bundle args = instrumentation.getArguments(); 219 mAm = ActivityManager.getService(); 220 String launchDirectory = args.getString(KEY_LAUNCH_DIRECTORY); 221 222 createMappings(); 223 parseArgs(args); 224 checkAccountSignIn(); 225 226 // Root directory for applaunch file to log the app launch output 227 // Will be useful in case of simpleperf command is used 228 File launchRootDir = null; 229 if (null != launchDirectory && !launchDirectory.isEmpty()) { 230 launchRootDir = new File(launchDirectory); 231 if (!launchRootDir.exists() && !launchRootDir.mkdirs()) { 232 throw new IOException("Unable to create the destination directory " 233 + launchRootDir + ". Try disabling selinux."); 234 } 235 } 236 237 try { 238 launchSubDir = new File(launchRootDir, LAUNCH_SUB_DIRECTORY); 239 240 if (!launchSubDir.exists() && !launchSubDir.mkdirs()) { 241 throw new IOException("Unable to create the lauch file sub directory " 242 + launchSubDir + ". Try disabling selinux."); 243 } 244 File file = new File(launchSubDir, LAUNCH_FILE); 245 FileOutputStream outputStream = new FileOutputStream(file); 246 mBufferedWriter = new BufferedWriter(new OutputStreamWriter( 247 outputStream)); 248 249 // Root directory for trace file during the launches 250 File rootTrace = null; 251 File rootTraceSubDir = null; 252 int traceBufferSize = 0; 253 int traceDumpInterval = 0; 254 Set<String> traceCategoriesSet = null; 255 if (null != mTraceDirectoryStr && !mTraceDirectoryStr.isEmpty()) { 256 rootTrace = new File(mTraceDirectoryStr); 257 if (!rootTrace.exists() && !rootTrace.mkdirs()) { 258 throw new IOException("Unable to create the trace directory"); 259 } 260 rootTraceSubDir = new File(rootTrace, TRACE_SUB_DIRECTORY); 261 if (!rootTraceSubDir.exists() && !rootTraceSubDir.mkdirs()) { 262 throw new IOException("Unable to create the trace sub directory"); 263 } 264 assertNotNull("Trace iteration parameter is mandatory", 265 args.getString(KEY_TRACE_ITERATIONS)); 266 mTraceLaunchCount = Integer.parseInt(args.getString(KEY_TRACE_ITERATIONS)); 267 String traceCategoriesStr = args 268 .getString(KEY_TRACE_CATEGORY, DEFAULT_TRACE_CATEGORIES); 269 traceBufferSize = Integer.parseInt(args.getString(KEY_TRACE_BUFFERSIZE, 270 DEFAULT_TRACE_BUFFER_SIZE)); 271 traceDumpInterval = Integer.parseInt(args.getString(KEY_TRACE_DUMPINTERVAL, 272 DEFAULT_TRACE_DUMP_INTERVAL)); 273 traceCategoriesSet = new HashSet<String>(); 274 if (!traceCategoriesStr.isEmpty()) { 275 String[] traceCategoriesSplit = traceCategoriesStr.split(DELIMITER); 276 for (int i = 0; i < traceCategoriesSplit.length; i++) { 277 traceCategoriesSet.add(traceCategoriesSplit[i]); 278 } 279 } 280 } 281 282 // Get the app launch order based on launch order, trial launch, 283 // launch iterations and trace iterations 284 setLaunchOrder(); 285 286 for (LaunchOrder launch : mLaunchOrderList) { 287 toggleIorapStatus(launch.getIorapEnabled()); 288 dropCache(/*override*/false); 289 290 Log.v(TAG, "Launch reason: " + launch.getLaunchReason()); 291 292 // App launch times for trial launch will not be used for final 293 // launch time calculations. 294 if (launch.getLaunchReason().equals(TRIAL_LAUNCH)) { 295 mIterationCycle = false; 296 // In the "applaunch.txt" file, trail launches is referenced using 297 // "TRIAL_LAUNCH" 298 Intent startIntent = mNameToIntent.get(launch.getApp()); 299 if (startIntent == null) { 300 Log.w(TAG, "App does not exist: " + launch.getApp()); 301 mResult.putString(mNameToResultKey.get(launch.getApp()), 302 "App does not exist"); 303 continue; 304 } 305 String appPkgName = startIntent.getComponent().getPackageName(); 306 if (SPEED_PROFILE_FILTER.equals(launch.getCompilerFilter())) { 307 assertTrue(String.format("Not able to compile the app : %s", appPkgName), 308 compileApp(VERIFY_FILTER, appPkgName)); 309 } else if (launch.getCompilerFilter() != null) { 310 assertTrue(String.format("Not able to compile the app : %s", appPkgName), 311 compileApp(launch.getCompilerFilter(), appPkgName)); 312 } 313 // We only need to run a trial for the speed-profile filter, but we always 314 // run one for "applaunch.txt" consistency. 315 AppLaunchResult launchResult = 316 startApp(launch.getApp(), launch.getLaunchReason()); 317 if (launchResult.mLaunchTime < 0) { 318 addLaunchResult(launch, new AppLaunchResult()); 319 // simply pass the app if launch isn't successful 320 // error should have already been logged by startApp 321 continue; 322 } 323 sleep(INITIAL_LAUNCH_IDLE_TIMEOUT); 324 if (SPEED_PROFILE_FILTER.equals(launch.getCompilerFilter())) { 325 // Send SIGUSR1 to force dumping a profile. 326 String sendSignalCommand = 327 String.format("killall -s SIGUSR1 %s", appPkgName); 328 getInstrumentation().getUiAutomation().executeShellCommand( 329 sendSignalCommand); 330 // killall is async, wait one second to let the app save the profile. 331 sleep(PROFILE_SAVE_SLEEP_TIMEOUT); 332 assertTrue(String.format("Not able to compile the app : %s", appPkgName), 333 compileApp(launch.getCompilerFilter(), appPkgName)); 334 } 335 } 336 else if (launch.getLaunchReason().startsWith(IORAP_TRIAL_LAUNCH)) { 337 mIterationCycle = false; 338 339 // In the "applaunch.txt" file, iorap-trial launches is referenced using 340 // "IORAP_TRIAL_LAUNCH" or "IORAP_TRIAL_LAUNCH_LAST" 341 Intent startIntent = mNameToIntent.get(launch.getApp()); 342 if (startIntent == null) { 343 Log.w(TAG, "App does not exist: " + launch.getApp()); 344 mResult.putString(mNameToResultKey.get(launch.getApp()), 345 "App does not exist"); 346 continue; 347 } 348 String appPkgName = startIntent.getComponent().getPackageName(); 349 350 if (launch.getLaunchReason().equals(IORAP_TRIAL_LAUNCH_FIRST)) { 351 // delete any iorap-traces associated with this package. 352 purgeIorapPackage(appPkgName); 353 } 354 dropCache(/*override*/true); // iorap-trial runs must have drop cache. 355 356 AppLaunchResult launchResult = 357 startApp(launch.getApp(), launch.getLaunchReason()); 358 if (launchResult.mLaunchTime < 0) { 359 addLaunchResult(launch, new AppLaunchResult()); 360 // simply pass the app if launch isn't successful 361 // error should have already been logged by startApp 362 continue; 363 } 364 // wait for slightly more than 5s (iorapd.perfetto.trace_duration_ms) for the trace buffers to complete. 365 sleep(IORAP_TRACE_DURATION_TIMEOUT); 366 367 if (launch.getLaunchReason().equals(IORAP_TRIAL_LAUNCH_LAST)) { 368 // run the iorap compiler and wait for iorap to compile fully. 369 // this throws an exception if it fails. 370 compileAppForIorapWithRetries(appPkgName, IORAP_COMPILE_RETRIES); 371 } 372 } 373 374 // App launch times used for final calculation 375 else if (launch.getLaunchReason().contains(LAUNCH_ITERATION_PREFIX)) { 376 mIterationCycle = true; 377 AppLaunchResult launchResults = null; 378 if (hasFailureOnFirstLaunch(launch)) { 379 // skip if the app has failures while launched first 380 continue; 381 } 382 AtraceLogger atraceLogger = null; 383 if (mTraceAll) { 384 Log.i(TAG, "Started tracing " + launch.getApp()); 385 atraceLogger = AtraceLogger 386 .getAtraceLoggerInstance(getInstrumentation()); 387 } 388 try { 389 // Start the trace 390 if (atraceLogger != null) { 391 atraceLogger.atraceStart(traceCategoriesSet, traceBufferSize, 392 traceDumpInterval, rootTraceSubDir, 393 String.format("%s-%s-%s", launch.getApp(), 394 launch.getCompilerFilter(), launch.getLaunchReason())); 395 } 396 // In the "applaunch.txt" file app launches are referenced using 397 // "LAUNCH_ITERATION - ITERATION NUM" 398 launchResults = startApp(launch.getApp(), launch.getLaunchReason()); 399 if (launchResults.mLaunchTime < 0) { 400 addLaunchResult(launch, new AppLaunchResult()); 401 // if it fails once, skip the rest of the launches 402 continue; 403 } else { 404 mCycleTime += launchResults.mLaunchTime; 405 addLaunchResult(launch, launchResults); 406 } 407 sleep(POST_LAUNCH_IDLE_TIMEOUT); 408 } finally { 409 // Stop the trace 410 if (atraceLogger != null) { 411 Log.i(TAG, "Stopped tracing " + launch.getApp()); 412 atraceLogger.atraceStop(); 413 } 414 } 415 416 } 417 418 // App launch times for trace launch will not be used for final 419 // launch time calculations. 420 else if (launch.getLaunchReason().contains(TRACE_ITERATION_PREFIX)) { 421 mIterationCycle = false; 422 AtraceLogger atraceLogger = AtraceLogger 423 .getAtraceLoggerInstance(getInstrumentation()); 424 // Start the trace 425 try { 426 atraceLogger.atraceStart(traceCategoriesSet, traceBufferSize, 427 traceDumpInterval, rootTraceSubDir, 428 String.format("%s-%s-%s", launch.getApp(), 429 launch.getCompilerFilter(), launch.getLaunchReason())); 430 startApp(launch.getApp(), launch.getLaunchReason()); 431 sleep(POST_LAUNCH_IDLE_TIMEOUT); 432 } finally { 433 // Stop the trace 434 atraceLogger.atraceStop(); 435 } 436 } 437 if(mForceStopApp) { 438 sleep(BEFORE_FORCE_STOP_SLEEP_TIMEOUT); 439 forceStopApp(launch.getApp()); 440 sleep(BEFORE_KILL_APP_SLEEP_TIMEOUT); 441 // Close again for good measure (just in case). 442 forceStopApp(launch.getApp()); 443 // Kill the backgrounded process in the case forceStopApp only sent it to 444 // background. 445 killBackgroundApp(launch.getApp()); 446 } else { 447 startHomeIntent(); 448 } 449 sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT); 450 451 // If cycle clean up is enabled and last app launched is 452 // current app then the cycle is completed and eligible for 453 // cleanup. 454 if (LAUNCH_ORDER_CYCLIC.equalsIgnoreCase(mLaunchOrder) && mCycleCleanUp 455 && launch.getApp().equalsIgnoreCase(mLastAppName)) { 456 // Kill all the apps and drop all the cache 457 cleanUpAfterCycle(); 458 if (mIterationCycle) { 459 // Save the previous cycle time and reset the cycle time to 0 460 mCycleTimes.append(String.format("%d,", mCycleTime)); 461 mCycleTime = 0; 462 } 463 } 464 } 465 } finally { 466 if (null != mBufferedWriter) { 467 mBufferedWriter.close(); 468 } 469 } 470 471 if (mCycleTimes.length() != 0) { 472 mResult.putString("Cycle_Times", mCycleTimes.toString()); 473 } 474 for (String app : mNameToResultKey.keySet()) { 475 for (String compilerFilter : mCompilerFilters) { 476 StringBuilder launchTimes = new StringBuilder(); 477 StringBuilder cpuCycles = new StringBuilder(); 478 StringBuilder majorFaults = new StringBuilder(); 479 for (AppLaunchResult result : mNameToLaunchTime.get(app).get(compilerFilter)) { 480 launchTimes.append(result.mLaunchTime); 481 launchTimes.append(","); 482 if (mSimplePerfAppOnly) { 483 cpuCycles.append(result.mCpuCycles); 484 cpuCycles.append(","); 485 majorFaults.append(result.mMajorFaults); 486 majorFaults.append(","); 487 } 488 } 489 String filterName = (compilerFilter == null) ? "" : ("-" + compilerFilter); 490 mResult.putString(mNameToResultKey.get(app) + filterName, launchTimes.toString()); 491 if (mSimplePerfAppOnly) { 492 mResult.putString(mNameToResultKey.get(app) + filterName + "-cpuCycles", 493 cpuCycles.toString()); 494 mResult.putString(mNameToResultKey.get(app) + filterName + "-majorFaults", 495 majorFaults.toString()); 496 } 497 } 498 } 499 instrumentation.sendStatus(0, mResult); 500 } 501 502 /** 503 * Compile the app package using compilerFilter and return true or false 504 * based on status of the compilation command. 505 */ compileApp(String compilerFilter, String appPkgName)506 private boolean compileApp(String compilerFilter, String appPkgName) throws IOException { 507 try (ParcelFileDescriptor result = getInstrumentation().getUiAutomation(). 508 executeShellCommand(String.format(COMPILE_CMD, compilerFilter, appPkgName)); 509 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( 510 new FileInputStream(result.getFileDescriptor())))) { 511 String line; 512 while ((line = bufferedReader.readLine()) != null) { 513 if (line.contains(COMPILE_SUCCESS)) { 514 return true; 515 } 516 } 517 return false; 518 } 519 } 520 521 /** 522 * Compile the app package using compilerFilter, 523 * retrying if the compilation command fails in between. 524 */ compileAppForIorapWithRetries(String appPkgName, int retries)525 private void compileAppForIorapWithRetries(String appPkgName, int retries) throws IOException { 526 for (int i = 0; i < retries; ++i) { 527 if (compileAppForIorap(appPkgName)) { 528 return; 529 } 530 sleep(1000); 531 } 532 533 throw new IllegalStateException("compileAppForIorapWithRetries: timed out after " 534 + retries + " retries"); 535 } 536 537 /** 538 * Compile the app package using compilerFilter and return true or false 539 * based on status of the compilation command. 540 */ compileAppForIorap(String appPkgName)541 private boolean compileAppForIorap(String appPkgName) throws IOException { 542 String logcatTimestamp = getTimeNowForLogcat(); 543 544 getInstrumentation().getUiAutomation(). 545 executeShellCommand(String.format(IORAP_COMPILE_CMD, appPkgName)); 546 547 int i = 0; 548 for (i = 0; i < IORAP_COMPILE_CMD_TIMEOUT; ++i) { 549 IorapCompilationStatus status = waitForIorapCompiled(appPkgName); 550 if (status == IorapCompilationStatus.COMPLETE) { 551 Log.v(TAG, "compileAppForIorap: success"); 552 logDumpsysIorapd(appPkgName); 553 break; 554 } else if (status == IorapCompilationStatus.INSUFFICIENT_TRACES) { 555 Log.e(TAG, "compileAppForIorap: failed due to insufficient traces"); 556 logDumpsysIorapd(appPkgName); 557 throw new IllegalStateException( 558 "compileAppForIorap: failed due to insufficient traces"); 559 } // else INCOMPLETE. keep asking iorapd if it's done yet. 560 sleep(1000); 561 } 562 563 if (i == IORAP_COMPILE_CMD_TIMEOUT) { 564 Log.e(TAG, "compileAppForIorap: failed due to timeout"); 565 logDumpsysIorapd(appPkgName); 566 return false; 567 } 568 569 return true; 570 } 571 572 /** Save the contents of $(adb shell dumpsys iorapd) to the launch_logs directory. */ logDumpsysIorapd(String packageName)573 private void logDumpsysIorapd(String packageName) throws IOException { 574 InstrumentationTestRunner instrumentation = 575 (InstrumentationTestRunner)getInstrumentation(); 576 Bundle args = instrumentation.getArguments(); 577 578 String launchDirectory = args.getString(KEY_LAUNCH_DIRECTORY); 579 580 // Root directory for applaunch file to log the app launch output 581 // Will be useful in case of simpleperf command is used 582 File launchRootDir = null; 583 if (null != launchDirectory && !launchDirectory.isEmpty()) { 584 launchRootDir = new File(launchDirectory); 585 if (!launchRootDir.exists() && !launchRootDir.mkdirs()) { 586 throw new IOException("Unable to create the destination directory " 587 + launchRootDir + ". Try disabling selinux."); 588 } 589 } else { 590 Log.w(TAG, "logDumpsysIorapd: Missing launch-directory arg"); 591 return; 592 } 593 594 File launchSubDir = new File(launchRootDir, LAUNCH_SUB_DIRECTORY); 595 596 if (!launchSubDir.exists() && !launchSubDir.mkdirs()) { 597 throw new IOException("Unable to create the lauch file sub directory " 598 + launchSubDir + ". Try disabling selinux."); 599 } 600 String path = "iorapd_dumpsys_" + packageName + "_" + System.nanoTime() + ".txt"; 601 File file = new File(launchSubDir, path); 602 try (FileOutputStream outputStream = new FileOutputStream(file); 603 BufferedWriter writer = new BufferedWriter( 604 new OutputStreamWriter(outputStream)); 605 ParcelFileDescriptor result = getInstrumentation().getUiAutomation(). 606 executeShellCommand(IORAP_DUMPSYS_CMD); 607 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( 608 new FileInputStream(result.getFileDescriptor())))) { 609 String line; 610 while ((line = bufferedReader.readLine()) != null) { 611 writer.write(line + "\n"); 612 } 613 } 614 615 Log.v(TAG, "logDumpsysIorapd: Saved to file: " + path); 616 } 617 618 enum IorapCompilationStatus { 619 INCOMPLETE, 620 COMPLETE, 621 INSUFFICIENT_TRACES, 622 } waitForIorapCompiled(String appPkgName)623 private IorapCompilationStatus waitForIorapCompiled(String appPkgName) throws IOException { 624 try (ParcelFileDescriptor result = getInstrumentation().getUiAutomation(). 625 executeShellCommand(IORAP_DUMPSYS_CMD); 626 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( 627 new FileInputStream(result.getFileDescriptor())))) { 628 String line; 629 String prevLine = ""; 630 while ((line = bufferedReader.readLine()) != null) { 631 // Match the indented VersionedComponentName string. 632 // " com.google.android.deskclock/com.android.deskclock.DeskClock@62000712" 633 // Note: spaces are meaningful here. 634 if (prevLine.contains(" " + appPkgName) && prevLine.contains("@")) { 635 // pre-requisite: 636 // Compiled Status: Raw traces pending compilation (3) 637 if (line.contains("Compiled Status: Usable compiled trace")) { 638 return IorapCompilationStatus.COMPLETE; 639 } else if (line.contains("Compiled Status: ") && 640 line.contains("more traces for compilation")) { 641 // Compiled Status: Need 1 more traces for compilation 642 // No amount of waiting will help here because there were 643 // insufficient traces made. 644 return IorapCompilationStatus.INSUFFICIENT_TRACES; 645 } 646 } 647 648 prevLine = line; 649 } 650 return IorapCompilationStatus.INCOMPLETE; 651 } 652 } 653 makeReasonForIorapTrialLaunch(int launchCount)654 private String makeReasonForIorapTrialLaunch(int launchCount) { 655 String reason = IORAP_TRIAL_LAUNCH; 656 if (launchCount == 0) { 657 reason = IORAP_TRIAL_LAUNCH_FIRST; 658 } 659 if (launchCount == IORAP_TRIAL_LAUNCH_ITERATIONS - 1) { 660 reason = IORAP_TRIAL_LAUNCH_LAST; 661 } 662 return reason; 663 } 664 shouldIncludeIorap(String compilerFilter)665 private boolean shouldIncludeIorap(String compilerFilter) { 666 if (!mIorapTrialLaunch) { 667 return false; 668 } 669 670 // No iorap compiler filters specified: treat all compiler filters as ok. 671 if (mIorapCompilerFilters == null) { 672 return true; 673 } 674 675 // iorap compiler filters specified: the compilerFilter must be in the whitelist. 676 if (mIorapCompilerFilters.indexOf(compilerFilter) != -1) { 677 return true; 678 } 679 680 return false; 681 } 682 683 /** 684 * If launch order is "cyclic" then apps will be launched one after the 685 * other for each iteration count. 686 * If launch order is "sequential" then each app will be launched for given number 687 * iterations at once before launching the other apps. 688 */ setLaunchOrder()689 private void setLaunchOrder() { 690 if (LAUNCH_ORDER_CYCLIC.equalsIgnoreCase(mLaunchOrder)) { 691 for (String compilerFilter : mCompilerFilters) { 692 if (mTrialLaunch) { 693 for (String app : mNameToResultKey.keySet()) { 694 mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, TRIAL_LAUNCH, /*iorapEnabled*/false)); 695 } 696 } 697 if (shouldIncludeIorap(compilerFilter)) { 698 for (int launchCount = 0; launchCount < IORAP_TRIAL_LAUNCH_ITERATIONS; ++launchCount) { 699 for (String app : mNameToResultKey.keySet()) { 700 String reason = makeReasonForIorapTrialLaunch(launchCount); 701 mLaunchOrderList.add( 702 new LaunchOrder(app, compilerFilter, 703 reason, 704 /*iorapEnabled*/true)); 705 } 706 } 707 } 708 for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) { 709 for (String app : mNameToResultKey.keySet()) { 710 mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, 711 String.format(LAUNCH_ITERATION, launchCount), 712 shouldIncludeIorap(compilerFilter))); 713 } 714 } 715 if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) { 716 for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) { 717 for (String app : mNameToResultKey.keySet()) { 718 mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, 719 String.format(TRACE_ITERATION, traceCount), 720 shouldIncludeIorap(compilerFilter))); 721 } 722 } 723 } 724 } 725 } else if (LAUNCH_ORDER_SEQUENTIAL.equalsIgnoreCase(mLaunchOrder)) { 726 for (String compilerFilter : mCompilerFilters) { 727 for (String app : mNameToResultKey.keySet()) { 728 if (mTrialLaunch) { 729 mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, TRIAL_LAUNCH, /*iorapEnabled*/false)); 730 } 731 if (shouldIncludeIorap(compilerFilter)) { 732 for (int launchCount = 0; launchCount < IORAP_TRIAL_LAUNCH_ITERATIONS; ++launchCount) { 733 String reason = makeReasonForIorapTrialLaunch(launchCount); 734 mLaunchOrderList.add( 735 new LaunchOrder(app, compilerFilter, 736 reason, 737 /*iorapEnabled*/true)); 738 } 739 } 740 for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) { 741 mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, 742 String.format(LAUNCH_ITERATION, launchCount), 743 shouldIncludeIorap(compilerFilter))); 744 } 745 if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) { 746 for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) { 747 mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, 748 String.format(TRACE_ITERATION, traceCount), 749 shouldIncludeIorap(compilerFilter))); 750 } 751 } 752 } 753 } 754 } else { 755 assertTrue("Launch order is not valid parameter", false); 756 } 757 } 758 dropCache(boolean override)759 private void dropCache(boolean override) { 760 if (mDropCache || override) { 761 assertNotNull("Issue in dropping the cache", 762 getInstrumentation().getUiAutomation() 763 .executeShellCommand(DROP_CACHE_SCRIPT)); 764 } 765 } 766 767 // [[ $(adb shell whoami) == "root" ]] checkIfRoot()768 private boolean checkIfRoot() throws IOException { 769 String total = ""; 770 try (ParcelFileDescriptor result = getInstrumentation().getUiAutomation(). 771 executeShellCommand("whoami"); 772 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( 773 new FileInputStream(result.getFileDescriptor())))) { 774 String line; 775 while ((line = bufferedReader.readLine()) != null) { 776 total = total + line; 777 } 778 } 779 return total.contains("root"); 780 } 781 stopIorapd()782 private void stopIorapd() { 783 getInstrumentation().getUiAutomation() 784 .executeShellCommand("stop iorapd"); 785 sleep(100); // give it extra time to fully stop. 786 } 787 startIorapd()788 private void startIorapd() { 789 String logcatTimeNow = getTimeNowForLogcat(); 790 Log.v(TAG, "startIorapd, logcat time: " + logcatTimeNow); 791 792 getInstrumentation().getUiAutomation() 793 .executeShellCommand("start iorapd"); 794 795 int maxAttempts = 100; 796 int attempt = 0; 797 do { 798 // Ensure that IorapForwardingService fully reconnects to iorapd before proceeding. 799 String needle = "Connected to iorapd native service"; 800 String logcatLines = getLogcatSinceTime(logcatTimeNow); 801 802 if (logcatLines.contains(needle)) { 803 break; 804 } 805 806 sleep(1000); 807 attempt++; 808 } while (attempt < maxAttempts); 809 810 if (attempt == maxAttempts) { 811 Log.e(TAG, "Timed out after waiting for iorapd to start"); 812 } 813 // Wait a little bit longer for iorapd to settle. 814 sleep(1000); 815 } 816 817 // Delete all db rows and files associated with a package in iorapd. 818 // Effectively deletes any raw or compiled trace files, unoptimizing the package in iorap. purgeIorapPackage(String packageName)819 private void purgeIorapPackage(String packageName) { 820 try { 821 if (!checkIfRoot()) { 822 throw new AssertionError("must be root to toggle iorapd; try adb root?"); 823 } 824 } catch (IOException e) { 825 throw new AssertionError(e); 826 } 827 828 Log.v(TAG, "Purge iorap package: " + packageName); 829 getInstrumentation().getUiAutomation() 830 .executeShellCommand(String.format(IORAP_MAINTENANCE_CMD, packageName)); 831 Log.v(TAG, "Executed: " + String.format(IORAP_MAINTENANCE_CMD, packageName)); 832 } 833 executeShellCommandWithTempFile(String cmd)834 String executeShellCommandWithTempFile(String cmd) { 835 Log.v(TAG, "executeShellCommandWithTempFile, cmd: " + cmd); 836 try { 837 //File outputDir = 838 // InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(); 839 File outputFile = File.createTempFile("exec_shell_command", ".sh"); 840 841 try { 842 outputFile.setWritable(true); 843 outputFile.setExecutable(true, /*ownersOnly*/false); 844 845 String scriptPath = outputFile.toString(); 846 847 // If this works correctly, the next log-line will print 'Success'. 848 try (BufferedWriter writer = new BufferedWriter(new FileWriter(scriptPath))) { 849 writer.write(cmd); 850 } 851 852 String resultString = ""; 853 try (ParcelFileDescriptor result = getInstrumentation().getUiAutomation(). 854 executeShellCommand(scriptPath); 855 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( 856 new FileInputStream(result.getFileDescriptor())))) { 857 String line; 858 while ((line = bufferedReader.readLine()) != null) { 859 resultString += line + "\n"; 860 } 861 } 862 863 return resultString; 864 } finally { 865 outputFile.delete(); 866 } 867 } catch (IOException e) { 868 throw new AssertionError("Failed to execute shell command: " + cmd, e); 869 } 870 } 871 872 // Get the 'now' timestamp usable with $(adb logcat -v utc -T "time string") getTimeNowForLogcat()873 String getTimeNowForLogcat() { 874 ZonedDateTime utc = ZonedDateTime.now(ZoneOffset.UTC); 875 876 // YYYY-MM-DD hh:mm:ss.mmm 877 return utc.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")); 878 } 879 getLogcatSinceTime(String logcatTime)880 String getLogcatSinceTime(String logcatTime) { 881 // The time has spaces in it but must be passed as a single arg. 882 // Therefore use a temp script file. 883 return executeShellCommandWithTempFile( 884 String.format("logcat -d -v threadtime -v utc -T '%s'", logcatTime)); 885 } 886 887 /** 888 * Toggle iorapd-based readahead and trace-collection. 889 * If iorapd is already enabled and enable is true, does nothing. 890 * If iorapd is already disabled and enable is false, does nothing. 891 */ toggleIorapStatus(boolean enable)892 private void toggleIorapStatus(boolean enable) { 893 boolean currentlyEnabled = false; 894 Log.v(TAG, "toggleIorapStatus " + Boolean.toString(enable)); 895 896 // Do nothing if we are already enabled or disabled. 897 if (mIorapStatus == IorapStatus.ENABLED && enable) { 898 return; 899 } else if (mIorapStatus == IorapStatus.DISABLED && !enable) { 900 return; 901 } 902 903 try { 904 if (!checkIfRoot()) { 905 throw new AssertionError("must be root to toggle iorapd; try adb root?"); 906 } 907 } catch (IOException e) { 908 throw new AssertionError(e); 909 } 910 911 getInstrumentation().getUiAutomation() 912 .executeShellCommand(String.format("setprop iorapd.perfetto.enable %b", enable)); 913 getInstrumentation().getUiAutomation() 914 .executeShellCommand(String.format("setprop iorapd.readahead.enable %b", enable)); 915 getInstrumentation().getUiAutomation() 916 .executeShellCommand(String.format( 917 "setprop iorapd.maintenance.min_traces %d", IORAP_COMPILE_MIN_TRACES)); 918 // this last command blocks until iorapd refreshes its system properties 919 getInstrumentation().getUiAutomation() 920 .executeShellCommand(String.format("dumpsys iorapd --refresh-properties")); 921 922 if (enable) { 923 mIorapStatus = IorapStatus.ENABLED; 924 } else { 925 mIorapStatus = IorapStatus.DISABLED; 926 } 927 } 928 parseArgs(Bundle args)929 private void parseArgs(Bundle args) { 930 mNameToResultKey = new LinkedHashMap<String, String>(); 931 mNameToLaunchTime = new HashMap<>(); 932 String launchIterations = args.getString(KEY_LAUNCH_ITERATIONS); 933 if (launchIterations != null) { 934 mLaunchIterations = Integer.parseInt(launchIterations); 935 } 936 String forceStopApp = args.getString(KEY_FORCE_STOP_APP); 937 938 if (forceStopApp != null) { 939 mForceStopApp = Boolean.parseBoolean(forceStopApp); 940 } 941 942 String enableRecording = args.getString(ENABLE_SCREEN_RECORDING); 943 944 if (enableRecording != null) { 945 mEnableRecording = Boolean.parseBoolean(enableRecording); 946 } 947 String appList = args.getString(KEY_APPS); 948 if (appList == null) 949 return; 950 951 String appNames[] = appList.split("\\|"); 952 for (String pair : appNames) { 953 String[] parts = pair.split("\\^"); 954 if (parts.length != 2) { 955 Log.e(TAG, "The apps key is incorrectly formatted"); 956 fail(); 957 } 958 959 mNameToResultKey.put(parts[0], parts[1]); 960 mNameToLaunchTime.put(parts[0], null); 961 mLastAppName = parts[0]; 962 } 963 String requiredAccounts = args.getString(KEY_REQUIRED_ACCOUNTS); 964 if (requiredAccounts != null) { 965 mRequiredAccounts = new HashSet<String>(); 966 for (String accountType : requiredAccounts.split(",")) { 967 mRequiredAccounts.add(accountType); 968 } 969 } 970 971 String compilerFilterList = args.getString(KEY_COMPILER_FILTERS); 972 if (compilerFilterList != null) { 973 // If a compiler filter is passed, we make a trial launch to force compilation 974 // of the apps. 975 mTrialLaunch = true; 976 mCompilerFilters = compilerFilterList.split("\\|"); 977 } else { 978 // Just pass a null compiler filter to use the current state of the app. 979 mCompilerFilters = new String[1]; 980 } 981 982 String iorapCompilerFilterList = args.getString(KEY_IORAP_COMPILER_FILTERS); 983 if (iorapCompilerFilterList != null) { 984 // Passing in iorap compiler filters implies an iorap trial launch. 985 mIorapTrialLaunch = true; 986 mIorapCompilerFilters = Arrays.asList(iorapCompilerFilterList.split("\\|")); 987 } 988 989 // Pre-populate the results map to avoid null checks. 990 for (String app : mNameToLaunchTime.keySet()) { 991 HashMap<String, List<AppLaunchResult>> map = new HashMap<>(); 992 mNameToLaunchTime.put(app, map); 993 for (String compilerFilter : mCompilerFilters) { 994 map.put(compilerFilter, new ArrayList<>()); 995 } 996 } 997 998 mTraceDirectoryStr = args.getString(KEY_TRACE_DIRECTORY); 999 mDropCache = Boolean.parseBoolean(args.getString(KEY_DROP_CACHE)); 1000 mSimplePerfCmd = args.getString(KEY_SIMPLEPERF_CMD); 1001 mLaunchOrder = args.getString(KEY_LAUNCH_ORDER, LAUNCH_ORDER_CYCLIC); 1002 mSimplePerfAppOnly = Boolean.parseBoolean(args.getString(KEY_SIMPLEPERF_APP)); 1003 mCycleCleanUp = Boolean.parseBoolean(args.getString(KEY_CYCLE_CLEAN)); 1004 mTraceAll = Boolean.parseBoolean(args.getString(KEY_TRACE_ALL)); 1005 mTrialLaunch = mTrialLaunch || Boolean.parseBoolean(args.getString(KEY_TRIAL_LAUNCH)); 1006 mIorapTrialLaunch = mIorapTrialLaunch || 1007 Boolean.parseBoolean(args.getString(KEY_IORAP_TRIAL_LAUNCH)); 1008 1009 if (mSimplePerfCmd != null && mSimplePerfAppOnly) { 1010 Log.w(TAG, String.format("Passing both %s and %s is not supported, ignoring %s", 1011 KEY_SIMPLEPERF_CMD, KEY_SIMPLEPERF_APP, KEY_SIMPLEPERF_CMD)); 1012 } 1013 } 1014 hasLeanback(Context context)1015 private boolean hasLeanback(Context context) { 1016 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); 1017 } 1018 createMappings()1019 private void createMappings() { 1020 mNameToIntent = new LinkedHashMap<String, Intent>(); 1021 1022 PackageManager pm = getInstrumentation().getContext() 1023 .getPackageManager(); 1024 Intent intentToResolve = new Intent(Intent.ACTION_MAIN); 1025 intentToResolve.addCategory(hasLeanback(getInstrumentation().getContext()) ? 1026 Intent.CATEGORY_LEANBACK_LAUNCHER : 1027 Intent.CATEGORY_LAUNCHER); 1028 List<ResolveInfo> ris = pm.queryIntentActivities(intentToResolve, 0); 1029 resolveLoop(ris, intentToResolve, pm); 1030 // For Wear 1031 intentToResolve = new Intent(WEARABLE_ACTION_GOOGLE); 1032 ris = pm.queryIntentActivities(intentToResolve, 0); 1033 resolveLoop(ris, intentToResolve, pm); 1034 } 1035 resolveLoop(List<ResolveInfo> ris, Intent intentToResolve, PackageManager pm)1036 private void resolveLoop(List<ResolveInfo> ris, Intent intentToResolve, PackageManager pm) { 1037 if (ris == null || ris.isEmpty()) { 1038 Log.i(TAG, "Could not find any apps"); 1039 } else { 1040 for (ResolveInfo ri : ris) { 1041 Intent startIntent = new Intent(intentToResolve); 1042 startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 1043 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 1044 startIntent.setClassName(ri.activityInfo.packageName, 1045 ri.activityInfo.name); 1046 String appName = ri.loadLabel(pm).toString(); 1047 if (appName != null) { 1048 // Support launching intent using package name or app name 1049 mNameToIntent.put(ri.activityInfo.packageName, startIntent); 1050 mNameToIntent.put(appName, startIntent); 1051 } 1052 } 1053 } 1054 } 1055 startApp(String appName, String launchReason)1056 private AppLaunchResult startApp(String appName, String launchReason) 1057 throws NameNotFoundException, RemoteException { 1058 Log.i(TAG, "Starting " + appName); 1059 if(mEnableRecording) { 1060 startRecording(appName, launchReason); 1061 } 1062 1063 Intent startIntent = mNameToIntent.get(appName); 1064 if (startIntent == null) { 1065 Log.w(TAG, "App does not exist: " + appName); 1066 mResult.putString(mNameToResultKey.get(appName), "App does not exist"); 1067 return new AppLaunchResult(); 1068 } 1069 AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent, launchReason); 1070 Thread t = new Thread(runnable); 1071 t.start(); 1072 try { 1073 t.join(JOIN_TIMEOUT); 1074 } catch (InterruptedException e) { 1075 // ignore 1076 } 1077 1078 if(mEnableRecording) { 1079 stopRecording(); 1080 } 1081 return runnable.getResult(); 1082 } 1083 checkAccountSignIn()1084 private void checkAccountSignIn() { 1085 // ensure that the device has the required account types before starting test 1086 // e.g. device must have a valid Google account sign in to measure a meaningful launch time 1087 // for Gmail 1088 if (mRequiredAccounts == null || mRequiredAccounts.isEmpty()) { 1089 return; 1090 } 1091 final AccountManager am = 1092 (AccountManager) getInstrumentation().getTargetContext().getSystemService( 1093 Context.ACCOUNT_SERVICE); 1094 Account[] accounts = am.getAccounts(); 1095 // use set here in case device has multiple accounts of the same type 1096 Set<String> foundAccounts = new HashSet<String>(); 1097 for (Account account : accounts) { 1098 if (mRequiredAccounts.contains(account.type)) { 1099 foundAccounts.add(account.type); 1100 } 1101 } 1102 // check if account type matches, if not, fail test with message on what account types 1103 // are missing 1104 if (mRequiredAccounts.size() != foundAccounts.size()) { 1105 mRequiredAccounts.removeAll(foundAccounts); 1106 StringBuilder sb = new StringBuilder("Device missing these accounts:"); 1107 for (String account : mRequiredAccounts) { 1108 sb.append(' '); 1109 sb.append(account); 1110 } 1111 fail(sb.toString()); 1112 } 1113 } 1114 startHomeIntent()1115 private void startHomeIntent() { 1116 Intent homeIntent = new Intent(Intent.ACTION_MAIN); 1117 homeIntent.addCategory(Intent.CATEGORY_HOME); 1118 homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 1119 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 1120 getInstrumentation().getContext().startActivity(homeIntent); 1121 sleep(POST_LAUNCH_IDLE_TIMEOUT); 1122 } 1123 cleanUpAfterCycle()1124 private void cleanUpAfterCycle() { 1125 // Kill all the apps 1126 for (String appName : mNameToIntent.keySet()) { 1127 Log.w(TAG, String.format("killing %s", appName)); 1128 forceStopApp(appName); 1129 } 1130 // Drop all the cache. 1131 assertNotNull("Issue in dropping the cache", 1132 getInstrumentation().getUiAutomation() 1133 .executeShellCommand(DROP_CACHE_SCRIPT)); 1134 } 1135 forceStopApp(String appName)1136 private void forceStopApp(String appName) { 1137 Intent startIntent = mNameToIntent.get(appName); 1138 if (startIntent != null) { 1139 String packageName = startIntent.getComponent().getPackageName(); 1140 try { 1141 mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT); 1142 } catch (RemoteException e) { 1143 Log.w(TAG, "Error closing app", e); 1144 } 1145 } 1146 } 1147 killBackgroundApp(String appName)1148 private void killBackgroundApp(String appName) { 1149 Intent startIntent = mNameToIntent.get(appName); 1150 if (startIntent != null) { 1151 String packageName = startIntent.getComponent().getPackageName(); 1152 try { 1153 mAm.killBackgroundProcesses(packageName, UserHandle.USER_CURRENT); 1154 } catch (RemoteException e) { 1155 Log.w(TAG, "Error closing app", e); 1156 } 1157 } 1158 } 1159 sleep(int time)1160 private void sleep(int time) { 1161 try { 1162 Thread.sleep(time); 1163 } catch (InterruptedException e) { 1164 // ignore 1165 } 1166 } 1167 reportError(String appName, String processName)1168 private void reportError(String appName, String processName) { 1169 ActivityManager am = (ActivityManager) getInstrumentation() 1170 .getContext().getSystemService(Context.ACTIVITY_SERVICE); 1171 List<ProcessErrorStateInfo> crashes = am.getProcessesInErrorState(); 1172 if (crashes != null) { 1173 for (ProcessErrorStateInfo crash : crashes) { 1174 if (!crash.processName.equals(processName)) 1175 continue; 1176 1177 Log.w(TAG, appName + " crashed: " + crash.shortMsg); 1178 mResult.putString(mNameToResultKey.get(appName), crash.shortMsg); 1179 return; 1180 } 1181 } 1182 1183 mResult.putString(mNameToResultKey.get(appName), 1184 "Crashed for unknown reason"); 1185 Log.w(TAG, appName 1186 + " not found in process list, most likely it is crashed"); 1187 } 1188 1189 private class LaunchOrder { 1190 private String mApp; 1191 private String mCompilerFilter; 1192 private String mLaunchReason; 1193 private boolean mIorapEnabled; 1194 LaunchOrder(String app, String compilerFilter, String launchReason, boolean iorapEnabled)1195 LaunchOrder(String app, String compilerFilter, String launchReason, boolean iorapEnabled) { 1196 mApp = app; 1197 mCompilerFilter = compilerFilter; 1198 mLaunchReason = launchReason; 1199 mIorapEnabled = iorapEnabled; 1200 } 1201 getApp()1202 public String getApp() { 1203 return mApp; 1204 } 1205 setApp(String app)1206 public void setApp(String app) { 1207 mApp = app; 1208 } 1209 getCompilerFilter()1210 public String getCompilerFilter() { 1211 return mCompilerFilter; 1212 } 1213 getLaunchReason()1214 public String getLaunchReason() { 1215 return mLaunchReason; 1216 } 1217 setLaunchReason(String launchReason)1218 public void setLaunchReason(String launchReason) { 1219 mLaunchReason = launchReason; 1220 } 1221 setIorapEnabled(boolean iorapEnabled)1222 public void setIorapEnabled(boolean iorapEnabled) { 1223 mIorapEnabled = iorapEnabled; 1224 } 1225 getIorapEnabled()1226 public boolean getIorapEnabled() { 1227 return mIorapEnabled; 1228 } 1229 } 1230 1231 private class AppLaunchResult { 1232 long mLaunchTime; 1233 long mCpuCycles; 1234 long mMajorFaults; 1235 AppLaunchResult()1236 AppLaunchResult() { 1237 mLaunchTime = -1L; 1238 mCpuCycles = -1L; 1239 mMajorFaults = -1L; 1240 } 1241 AppLaunchResult(String launchTime, String cpuCycles, String majorFaults)1242 AppLaunchResult(String launchTime, String cpuCycles, String majorFaults) { 1243 try { 1244 mLaunchTime = Long.parseLong(launchTime, 10); 1245 mCpuCycles = Long.parseLong(cpuCycles, 10); 1246 mMajorFaults = Long.parseLong(majorFaults, 10); 1247 } catch (NumberFormatException e) { 1248 Log.e(TAG, "Error parsing result", e); 1249 } 1250 } 1251 } 1252 1253 private class AppLaunchRunnable implements Runnable { 1254 private Intent mLaunchIntent; 1255 private AppLaunchResult mLaunchResult; 1256 private String mLaunchReason; 1257 AppLaunchRunnable(Intent intent, String launchReason)1258 public AppLaunchRunnable(Intent intent, String launchReason) { 1259 mLaunchIntent = intent; 1260 mLaunchReason = launchReason; 1261 mLaunchResult = new AppLaunchResult(); 1262 } 1263 getResult()1264 public AppLaunchResult getResult() { 1265 return mLaunchResult; 1266 } 1267 run()1268 public void run() { 1269 File launchFile = null; 1270 try { 1271 String packageName = mLaunchIntent.getComponent().getPackageName(); 1272 String componentName = mLaunchIntent.getComponent().flattenToShortString(); 1273 if (mForceStopApp) { 1274 mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT); 1275 } 1276 String launchCmd = String.format("%s %s", APP_LAUNCH_CMD, componentName); 1277 if (mSimplePerfAppOnly) { 1278 try { 1279 // executeShellCommand cannot handle shell specific actions, like '&'. 1280 // Therefore, we create a file containing the command and make that 1281 // the command to launch. 1282 launchFile = File.createTempFile(LAUNCH_SCRIPT_NAME, ".sh"); 1283 launchFile.setExecutable(true); 1284 try (FileOutputStream stream = new FileOutputStream(launchFile); 1285 BufferedWriter writer = 1286 new BufferedWriter(new OutputStreamWriter(stream))) { 1287 String cmd = String.format(SIMPLEPERF_APP_CMD, packageName, launchCmd); 1288 // In the file, we need to escape any "$". 1289 cmd = cmd.replace("$", "\\$"); 1290 writer.write(cmd); 1291 } 1292 launchCmd = launchFile.getAbsolutePath(); 1293 } catch (IOException e) { 1294 Log.w(TAG, "Error writing the launch command", e); 1295 return; 1296 } 1297 } else if (null != mSimplePerfCmd) { 1298 launchCmd = String.format("%s %s", mSimplePerfCmd, launchCmd); 1299 } 1300 Log.v(TAG, "Final launch cmd:" + launchCmd); 1301 ParcelFileDescriptor parcelDesc = getInstrumentation().getUiAutomation() 1302 .executeShellCommand(launchCmd); 1303 mLaunchResult = parseLaunchTimeAndWrite(parcelDesc, String.format 1304 ("App Launch :%s %s", componentName, mLaunchReason)); 1305 } catch (RemoteException e) { 1306 Log.w(TAG, "Error launching app", e); 1307 } finally { 1308 if (launchFile != null) { 1309 launchFile.delete(); 1310 } 1311 } 1312 } 1313 1314 /** 1315 * Method to parse the launch time info and write the result to file 1316 * 1317 * @param parcelDesc 1318 * @return 1319 */ parseLaunchTimeAndWrite(ParcelFileDescriptor parcelDesc, String headerInfo)1320 private AppLaunchResult parseLaunchTimeAndWrite(ParcelFileDescriptor parcelDesc, 1321 String headerInfo) { 1322 String launchTime = "-1"; 1323 String cpuCycles = "-1"; 1324 String majorFaults = "-1"; 1325 boolean launchSuccess = false; 1326 try { 1327 InputStream inputStream = new FileInputStream(parcelDesc.getFileDescriptor()); 1328 /* SAMPLE OUTPUT : Cold launch 1329 Starting: Intent { cmp=com.google.android.calculator/com.android.calculator2.Calculator } 1330 Status: ok 1331 LaunchState: COLD 1332 Activity: com.google.android.calculator/com.android.calculator2.Calculator 1333 TotalTime: 357 1334 WaitTime: 377 1335 Complete*/ 1336 /* SAMPLE OUTPUT : Hot launch 1337 Starting: Intent { cmp=com.google.android.calculator/com.android.calculator2.Calculator } 1338 Warning: Activity not started, its current task has been brought to the front 1339 Status: ok 1340 LaunchState: HOT 1341 Activity: com.google.android.calculator/com.android.calculator2.CalculatorGoogle 1342 TotalTime: 60 1343 WaitTime: 67 1344 Complete*/ 1345 /* WITH SIMPLEPERF : 1346 Performance counter statistics, 1347 6595722690,cpu-cycles,4.511040,GHz,(100%), 1348 0,major-faults,0.000,/sec,(100%), 1349 Total test time,1.462129,seconds,*/ 1350 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( 1351 inputStream)); 1352 String line; 1353 mBufferedWriter.newLine(); 1354 mBufferedWriter.write(headerInfo); 1355 mBufferedWriter.newLine(); 1356 while ((line = bufferedReader.readLine()) != null) { 1357 mBufferedWriter.write(line); 1358 mBufferedWriter.newLine(); 1359 if (line.startsWith(SUCCESS_MESSAGE)) { 1360 launchSuccess = true; 1361 } 1362 if (!launchSuccess) { 1363 continue; 1364 } 1365 // Parse TotalTime which is the launch time 1366 if (line.startsWith(TOTAL_TIME_MESSAGE)) { 1367 String launchSplit[] = line.split(":"); 1368 launchTime = launchSplit[1].trim(); 1369 } 1370 1371 if (mSimplePerfAppOnly) { 1372 if (line.contains(",cpu-cycles,")) { 1373 cpuCycles = line.split(",")[0].trim(); 1374 } else if (line.contains(",major-faults,")) { 1375 majorFaults = line.split(",")[0].trim(); 1376 } 1377 } 1378 } 1379 mBufferedWriter.flush(); 1380 inputStream.close(); 1381 } catch (IOException e) { 1382 Log.w(TAG, "Error parsing launch time and writing to file", e); 1383 } 1384 return new AppLaunchResult(launchTime, cpuCycles, majorFaults); 1385 } 1386 1387 } 1388 1389 /** 1390 * Start the screen recording while launching the app. 1391 * 1392 * @param appName 1393 * @param launchReason 1394 */ startRecording(String appName, String launchReason)1395 private void startRecording(String appName, String launchReason) { 1396 Log.v(TAG, "Started Recording"); 1397 mCurrentThread = new RecordingThread("test-screen-record", 1398 String.format("%s_%s", appName, launchReason)); 1399 mCurrentThread.start(); 1400 } 1401 1402 /** 1403 * Stop already started screen recording. 1404 */ stopRecording()1405 private void stopRecording() { 1406 // Skip if not directory. 1407 if (launchSubDir == null) { 1408 return; 1409 } 1410 1411 // Add some extra time to the video end. 1412 SystemClock.sleep(VIDEO_TAIL_BUFFER); 1413 // Ctrl + C all screen record processes. 1414 mCurrentThread.cancel(); 1415 // Wait for the thread to completely die. 1416 try { 1417 mCurrentThread.join(); 1418 } catch (InterruptedException ex) { 1419 Log.e(TAG, "Interrupted when joining the recording thread.", ex); 1420 } 1421 Log.v(TAG, "Stopped Recording"); 1422 } 1423 1424 /** Returns the recording's name for part {@code part} of launch description. */ getOutputFile(String description, int part)1425 private File getOutputFile(String description, int part) { 1426 // Omit the iteration number for the first iteration. 1427 final String fileName = 1428 String.format( 1429 "%s-video%s.mp4", description, part == 1 ? "" : part); 1430 return Paths.get(launchSubDir.getAbsolutePath(), description).toFile(); 1431 } 1432 1433 1434 /** 1435 * Encapsulates the start and stop screen recording logic. 1436 * Copied from ScreenRecordCollector. 1437 */ 1438 private class RecordingThread extends Thread { 1439 private final String mDescription; 1440 private final List<File> mRecordings; 1441 1442 private boolean mContinue; 1443 RecordingThread(String name, String description)1444 public RecordingThread(String name, String description) { 1445 super(name); 1446 1447 mContinue = true; 1448 mRecordings = new ArrayList<>(); 1449 1450 assertNotNull("No test description provided for recording.", description); 1451 mDescription = description; 1452 } 1453 1454 @Override run()1455 public void run() { 1456 try { 1457 // Start at i = 1 to encode parts as X.mp4, X2.mp4, X3.mp4, etc. 1458 for (int i = 1; i <= MAX_RECORDING_PARTS && mContinue; i++) { 1459 File output = getOutputFile(mDescription, i); 1460 Log.d( 1461 TAG, 1462 String.format("Recording screen to %s", output.getAbsolutePath())); 1463 mRecordings.add(output); 1464 // Make sure not to block on this background command in the main thread so 1465 // that the test continues to run, but block in this thread so it does not 1466 // trigger a new screen recording session before the prior one completes. 1467 getDevice().executeShellCommand( 1468 String.format("screenrecord %s", output.getAbsolutePath())); 1469 } 1470 } catch (IOException e) { 1471 throw new RuntimeException("Caught exception while screen recording."); 1472 } 1473 } 1474 cancel()1475 public void cancel() { 1476 mContinue = false; 1477 1478 // Identify the screenrecord PIDs and send SIGINT 2 (Ctrl + C) to each. 1479 try { 1480 String[] pids = getDevice().executeShellCommand( 1481 "pidof screenrecord").split(" "); 1482 for (String pid : pids) { 1483 // Avoid empty process ids, because of weird splitting behavior. 1484 if (pid.isEmpty()) { 1485 continue; 1486 } 1487 1488 getDevice().executeShellCommand( 1489 String.format("kill -2 %s", pid)); 1490 Log.d( 1491 TAG, 1492 String.format("Sent SIGINT 2 to screenrecord process (%s)", pid)); 1493 } 1494 } catch (IOException e) { 1495 throw new RuntimeException("Failed to kill screen recording process."); 1496 } 1497 } 1498 getRecordings()1499 public List<File> getRecordings() { 1500 return mRecordings; 1501 } 1502 } 1503 getDevice()1504 public UiDevice getDevice() { 1505 if (mDevice == null) { 1506 mDevice = UiDevice.getInstance(getInstrumentation()); 1507 } 1508 return mDevice; 1509 } 1510 } 1511