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 = 5; // min 5 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 Log.w(TAG, "Has failures on first launch: " + launch.getApp()); 381 forceStopApp(launch.getApp()); 382 continue; 383 } 384 AtraceLogger atraceLogger = null; 385 if (mTraceAll) { 386 Log.i(TAG, "Started tracing " + launch.getApp()); 387 atraceLogger = AtraceLogger 388 .getAtraceLoggerInstance(getInstrumentation()); 389 } 390 try { 391 // Start the trace 392 if (atraceLogger != null) { 393 atraceLogger.atraceStart(traceCategoriesSet, traceBufferSize, 394 traceDumpInterval, rootTraceSubDir, 395 String.format("%s-%s-%s", launch.getApp(), 396 launch.getCompilerFilter(), launch.getLaunchReason())); 397 } 398 // In the "applaunch.txt" file app launches are referenced using 399 // "LAUNCH_ITERATION - ITERATION NUM" 400 launchResults = startApp(launch.getApp(), launch.getLaunchReason()); 401 if (launchResults.mLaunchTime < 0) { 402 addLaunchResult(launch, new AppLaunchResult()); 403 // if it fails once, skip the rest of the launches 404 continue; 405 } else { 406 mCycleTime += launchResults.mLaunchTime; 407 addLaunchResult(launch, launchResults); 408 } 409 sleep(POST_LAUNCH_IDLE_TIMEOUT); 410 } finally { 411 // Stop the trace 412 if (atraceLogger != null) { 413 Log.i(TAG, "Stopped tracing " + launch.getApp()); 414 atraceLogger.atraceStop(); 415 } 416 } 417 418 } 419 420 // App launch times for trace launch will not be used for final 421 // launch time calculations. 422 else if (launch.getLaunchReason().contains(TRACE_ITERATION_PREFIX)) { 423 mIterationCycle = false; 424 AtraceLogger atraceLogger = AtraceLogger 425 .getAtraceLoggerInstance(getInstrumentation()); 426 // Start the trace 427 try { 428 atraceLogger.atraceStart(traceCategoriesSet, traceBufferSize, 429 traceDumpInterval, rootTraceSubDir, 430 String.format("%s-%s-%s", launch.getApp(), 431 launch.getCompilerFilter(), launch.getLaunchReason())); 432 startApp(launch.getApp(), launch.getLaunchReason()); 433 sleep(POST_LAUNCH_IDLE_TIMEOUT); 434 } finally { 435 // Stop the trace 436 atraceLogger.atraceStop(); 437 } 438 } 439 if(mForceStopApp) { 440 sleep(BEFORE_FORCE_STOP_SLEEP_TIMEOUT); 441 forceStopApp(launch.getApp()); 442 sleep(BEFORE_KILL_APP_SLEEP_TIMEOUT); 443 // Close again for good measure (just in case). 444 forceStopApp(launch.getApp()); 445 // Kill the backgrounded process in the case forceStopApp only sent it to 446 // background. 447 killBackgroundApp(launch.getApp()); 448 } else { 449 startHomeIntent(); 450 } 451 sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT); 452 453 // If cycle clean up is enabled and last app launched is 454 // current app then the cycle is completed and eligible for 455 // cleanup. 456 if (LAUNCH_ORDER_CYCLIC.equalsIgnoreCase(mLaunchOrder) && mCycleCleanUp 457 && launch.getApp().equalsIgnoreCase(mLastAppName)) { 458 // Kill all the apps and drop all the cache 459 cleanUpAfterCycle(); 460 if (mIterationCycle) { 461 // Save the previous cycle time and reset the cycle time to 0 462 mCycleTimes.append(String.format("%d,", mCycleTime)); 463 mCycleTime = 0; 464 } 465 } 466 } 467 } finally { 468 if (null != mBufferedWriter) { 469 mBufferedWriter.close(); 470 } 471 } 472 473 if (mCycleTimes.length() != 0) { 474 mResult.putString("Cycle_Times", mCycleTimes.toString()); 475 } 476 for (String app : mNameToResultKey.keySet()) { 477 for (String compilerFilter : mCompilerFilters) { 478 StringBuilder launchTimes = new StringBuilder(); 479 StringBuilder cpuCycles = new StringBuilder(); 480 StringBuilder majorFaults = new StringBuilder(); 481 for (AppLaunchResult result : mNameToLaunchTime.get(app).get(compilerFilter)) { 482 launchTimes.append(result.mLaunchTime); 483 launchTimes.append(","); 484 if (mSimplePerfAppOnly) { 485 cpuCycles.append(result.mCpuCycles); 486 cpuCycles.append(","); 487 majorFaults.append(result.mMajorFaults); 488 majorFaults.append(","); 489 } 490 } 491 String filterName = (compilerFilter == null) ? "" : ("-" + compilerFilter); 492 mResult.putString(mNameToResultKey.get(app) + filterName, launchTimes.toString()); 493 if (mSimplePerfAppOnly) { 494 mResult.putString(mNameToResultKey.get(app) + filterName + "-cpuCycles", 495 cpuCycles.toString()); 496 mResult.putString(mNameToResultKey.get(app) + filterName + "-majorFaults", 497 majorFaults.toString()); 498 } 499 } 500 } 501 instrumentation.sendStatus(0, mResult); 502 } 503 504 /** 505 * Compile the app package using compilerFilter and return true or false 506 * based on status of the compilation command. 507 */ compileApp(String compilerFilter, String appPkgName)508 private boolean compileApp(String compilerFilter, String appPkgName) throws IOException { 509 try (ParcelFileDescriptor result = getInstrumentation().getUiAutomation(). 510 executeShellCommand(String.format(COMPILE_CMD, compilerFilter, appPkgName)); 511 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( 512 new FileInputStream(result.getFileDescriptor())))) { 513 String line; 514 while ((line = bufferedReader.readLine()) != null) { 515 if (line.contains(COMPILE_SUCCESS)) { 516 return true; 517 } 518 } 519 return false; 520 } 521 } 522 523 /** 524 * Compile the app package using compilerFilter, 525 * retrying if the compilation command fails in between. 526 */ compileAppForIorapWithRetries(String appPkgName, int retries)527 private void compileAppForIorapWithRetries(String appPkgName, int retries) throws IOException { 528 for (int i = 0; i < retries; ++i) { 529 if (compileAppForIorap(appPkgName)) { 530 return; 531 } 532 sleep(1000); 533 } 534 535 throw new IllegalStateException("compileAppForIorapWithRetries: timed out after " 536 + retries + " retries"); 537 } 538 539 /** 540 * Compile the app package using compilerFilter and return true or false 541 * based on status of the compilation command. 542 */ compileAppForIorap(String appPkgName)543 private boolean compileAppForIorap(String appPkgName) throws IOException { 544 String logcatTimestamp = getTimeNowForLogcat(); 545 546 getInstrumentation().getUiAutomation(). 547 executeShellCommand(String.format(IORAP_COMPILE_CMD, appPkgName)); 548 549 int i = 0; 550 for (i = 0; i < IORAP_COMPILE_CMD_TIMEOUT; ++i) { 551 IorapCompilationStatus status = waitForIorapCompiled(appPkgName); 552 if (status == IorapCompilationStatus.COMPLETE) { 553 Log.v(TAG, "compileAppForIorap: success"); 554 logDumpsysIorapd(appPkgName); 555 break; 556 } else if (status == IorapCompilationStatus.INSUFFICIENT_TRACES) { 557 Log.e(TAG, "compileAppForIorap: failed due to insufficient traces"); 558 logDumpsysIorapd(appPkgName); 559 throw new IllegalStateException( 560 "compileAppForIorap: failed due to insufficient traces"); 561 } // else INCOMPLETE. keep asking iorapd if it's done yet. 562 sleep(1000); 563 } 564 565 if (i == IORAP_COMPILE_CMD_TIMEOUT) { 566 Log.e(TAG, "compileAppForIorap: failed due to timeout"); 567 logDumpsysIorapd(appPkgName); 568 return false; 569 } 570 571 return true; 572 } 573 574 /** Save the contents of $(adb shell dumpsys iorapd) to the launch_logs directory. */ logDumpsysIorapd(String packageName)575 private void logDumpsysIorapd(String packageName) throws IOException { 576 InstrumentationTestRunner instrumentation = 577 (InstrumentationTestRunner)getInstrumentation(); 578 Bundle args = instrumentation.getArguments(); 579 580 String launchDirectory = args.getString(KEY_LAUNCH_DIRECTORY); 581 582 // Root directory for applaunch file to log the app launch output 583 // Will be useful in case of simpleperf command is used 584 File launchRootDir = null; 585 if (null != launchDirectory && !launchDirectory.isEmpty()) { 586 launchRootDir = new File(launchDirectory); 587 if (!launchRootDir.exists() && !launchRootDir.mkdirs()) { 588 throw new IOException("Unable to create the destination directory " 589 + launchRootDir + ". Try disabling selinux."); 590 } 591 } else { 592 Log.w(TAG, "logDumpsysIorapd: Missing launch-directory arg"); 593 return; 594 } 595 596 File launchSubDir = new File(launchRootDir, LAUNCH_SUB_DIRECTORY); 597 598 if (!launchSubDir.exists() && !launchSubDir.mkdirs()) { 599 throw new IOException("Unable to create the lauch file sub directory " 600 + launchSubDir + ". Try disabling selinux."); 601 } 602 String path = "iorapd_dumpsys_" + packageName + "_" + System.nanoTime() + ".txt"; 603 File file = new File(launchSubDir, path); 604 try (FileOutputStream outputStream = new FileOutputStream(file); 605 BufferedWriter writer = new BufferedWriter( 606 new OutputStreamWriter(outputStream)); 607 ParcelFileDescriptor result = getInstrumentation().getUiAutomation(). 608 executeShellCommand(IORAP_DUMPSYS_CMD); 609 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( 610 new FileInputStream(result.getFileDescriptor())))) { 611 String line; 612 while ((line = bufferedReader.readLine()) != null) { 613 writer.write(line + "\n"); 614 } 615 } 616 617 Log.v(TAG, "logDumpsysIorapd: Saved to file: " + path); 618 } 619 620 enum IorapCompilationStatus { 621 INCOMPLETE, 622 COMPLETE, 623 INSUFFICIENT_TRACES, 624 } waitForIorapCompiled(String appPkgName)625 private IorapCompilationStatus waitForIorapCompiled(String appPkgName) throws IOException { 626 try (ParcelFileDescriptor result = getInstrumentation().getUiAutomation(). 627 executeShellCommand(IORAP_DUMPSYS_CMD); 628 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( 629 new FileInputStream(result.getFileDescriptor())))) { 630 String line; 631 String prevLine = ""; 632 while ((line = bufferedReader.readLine()) != null) { 633 // Match the indented VersionedComponentName string. 634 // " com.google.android.deskclock/com.android.deskclock.DeskClock@62000712" 635 // Note: spaces are meaningful here. 636 if (prevLine.contains(" " + appPkgName) && prevLine.contains("@")) { 637 // pre-requisite: 638 // Compiled Status: Raw traces pending compilation (3) 639 if (line.contains("Compiled Status: Usable compiled trace")) { 640 return IorapCompilationStatus.COMPLETE; 641 } else if (line.contains("Compiled Status: ") && 642 line.contains("more traces for compilation")) { 643 // Compiled Status: Need 1 more traces for compilation 644 // No amount of waiting will help here because there were 645 // insufficient traces made. 646 return IorapCompilationStatus.INSUFFICIENT_TRACES; 647 } 648 } 649 650 prevLine = line; 651 } 652 return IorapCompilationStatus.INCOMPLETE; 653 } 654 } 655 makeReasonForIorapTrialLaunch(int launchCount)656 private String makeReasonForIorapTrialLaunch(int launchCount) { 657 String reason = IORAP_TRIAL_LAUNCH; 658 if (launchCount == 0) { 659 reason = IORAP_TRIAL_LAUNCH_FIRST; 660 } 661 if (launchCount == IORAP_TRIAL_LAUNCH_ITERATIONS - 1) { 662 reason = IORAP_TRIAL_LAUNCH_LAST; 663 } 664 return reason; 665 } 666 shouldIncludeIorap(String compilerFilter)667 private boolean shouldIncludeIorap(String compilerFilter) { 668 if (!mIorapTrialLaunch) { 669 return false; 670 } 671 672 // No iorap compiler filters specified: treat all compiler filters as ok. 673 if (mIorapCompilerFilters == null) { 674 return true; 675 } 676 677 // iorap compiler filters specified: the compilerFilter must be in the whitelist. 678 if (mIorapCompilerFilters.indexOf(compilerFilter) != -1) { 679 return true; 680 } 681 682 return false; 683 } 684 685 /** 686 * If launch order is "cyclic" then apps will be launched one after the 687 * other for each iteration count. 688 * If launch order is "sequential" then each app will be launched for given number 689 * iterations at once before launching the other apps. 690 */ setLaunchOrder()691 private void setLaunchOrder() { 692 if (LAUNCH_ORDER_CYCLIC.equalsIgnoreCase(mLaunchOrder)) { 693 for (String compilerFilter : mCompilerFilters) { 694 if (mTrialLaunch) { 695 for (String app : mNameToResultKey.keySet()) { 696 mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, TRIAL_LAUNCH, /*iorapEnabled*/false)); 697 } 698 } 699 if (shouldIncludeIorap(compilerFilter)) { 700 for (int launchCount = 0; launchCount < IORAP_TRIAL_LAUNCH_ITERATIONS; ++launchCount) { 701 for (String app : mNameToResultKey.keySet()) { 702 String reason = makeReasonForIorapTrialLaunch(launchCount); 703 mLaunchOrderList.add( 704 new LaunchOrder(app, compilerFilter, 705 reason, 706 /*iorapEnabled*/true)); 707 } 708 } 709 } 710 for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) { 711 for (String app : mNameToResultKey.keySet()) { 712 mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, 713 String.format(LAUNCH_ITERATION, launchCount), 714 shouldIncludeIorap(compilerFilter))); 715 } 716 } 717 if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) { 718 for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) { 719 for (String app : mNameToResultKey.keySet()) { 720 mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, 721 String.format(TRACE_ITERATION, traceCount), 722 shouldIncludeIorap(compilerFilter))); 723 } 724 } 725 } 726 } 727 } else if (LAUNCH_ORDER_SEQUENTIAL.equalsIgnoreCase(mLaunchOrder)) { 728 for (String compilerFilter : mCompilerFilters) { 729 for (String app : mNameToResultKey.keySet()) { 730 if (mTrialLaunch) { 731 mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, TRIAL_LAUNCH, /*iorapEnabled*/false)); 732 } 733 if (shouldIncludeIorap(compilerFilter)) { 734 for (int launchCount = 0; launchCount < IORAP_TRIAL_LAUNCH_ITERATIONS; ++launchCount) { 735 String reason = makeReasonForIorapTrialLaunch(launchCount); 736 mLaunchOrderList.add( 737 new LaunchOrder(app, compilerFilter, 738 reason, 739 /*iorapEnabled*/true)); 740 } 741 } 742 for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) { 743 mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, 744 String.format(LAUNCH_ITERATION, launchCount), 745 shouldIncludeIorap(compilerFilter))); 746 } 747 if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) { 748 for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) { 749 mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, 750 String.format(TRACE_ITERATION, traceCount), 751 shouldIncludeIorap(compilerFilter))); 752 } 753 } 754 } 755 } 756 } else { 757 assertTrue("Launch order is not valid parameter", false); 758 } 759 } 760 dropCache(boolean override)761 private void dropCache(boolean override) { 762 if (mDropCache || override) { 763 assertNotNull("Issue in dropping the cache", 764 getInstrumentation().getUiAutomation() 765 .executeShellCommand(DROP_CACHE_SCRIPT)); 766 } 767 } 768 769 // [[ $(adb shell whoami) == "root" ]] checkIfRoot()770 private boolean checkIfRoot() throws IOException { 771 String total = ""; 772 try (ParcelFileDescriptor result = getInstrumentation().getUiAutomation(). 773 executeShellCommand("whoami"); 774 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( 775 new FileInputStream(result.getFileDescriptor())))) { 776 String line; 777 while ((line = bufferedReader.readLine()) != null) { 778 total = total + line; 779 } 780 } 781 return total.contains("root"); 782 } 783 stopIorapd()784 private void stopIorapd() { 785 getInstrumentation().getUiAutomation() 786 .executeShellCommand("stop iorapd"); 787 sleep(100); // give it extra time to fully stop. 788 } 789 startIorapd()790 private void startIorapd() { 791 String logcatTimeNow = getTimeNowForLogcat(); 792 Log.v(TAG, "startIorapd, logcat time: " + logcatTimeNow); 793 794 getInstrumentation().getUiAutomation() 795 .executeShellCommand("start iorapd"); 796 797 int maxAttempts = 100; 798 int attempt = 0; 799 do { 800 // Ensure that IorapForwardingService fully reconnects to iorapd before proceeding. 801 String needle = "Connected to iorapd native service"; 802 String logcatLines = getLogcatSinceTime(logcatTimeNow); 803 804 if (logcatLines.contains(needle)) { 805 break; 806 } 807 808 sleep(1000); 809 attempt++; 810 } while (attempt < maxAttempts); 811 812 if (attempt == maxAttempts) { 813 Log.e(TAG, "Timed out after waiting for iorapd to start"); 814 } 815 // Wait a little bit longer for iorapd to settle. 816 sleep(1000); 817 } 818 819 // Delete all db rows and files associated with a package in iorapd. 820 // Effectively deletes any raw or compiled trace files, unoptimizing the package in iorap. purgeIorapPackage(String packageName)821 private void purgeIorapPackage(String packageName) { 822 try { 823 if (!checkIfRoot()) { 824 throw new AssertionError("must be root to toggle iorapd; try adb root?"); 825 } 826 } catch (IOException e) { 827 throw new AssertionError(e); 828 } 829 830 Log.v(TAG, "Purge iorap package: " + packageName); 831 getInstrumentation().getUiAutomation() 832 .executeShellCommand(String.format(IORAP_MAINTENANCE_CMD, packageName)); 833 Log.v(TAG, "Executed: " + String.format(IORAP_MAINTENANCE_CMD, packageName)); 834 } 835 executeShellCommandWithTempFile(String cmd)836 String executeShellCommandWithTempFile(String cmd) { 837 Log.v(TAG, "executeShellCommandWithTempFile, cmd: " + cmd); 838 try { 839 //File outputDir = 840 // InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(); 841 File outputFile = File.createTempFile("exec_shell_command", ".sh"); 842 843 try { 844 outputFile.setWritable(true); 845 outputFile.setExecutable(true, /*ownersOnly*/false); 846 847 String scriptPath = outputFile.toString(); 848 849 // If this works correctly, the next log-line will print 'Success'. 850 try (BufferedWriter writer = new BufferedWriter(new FileWriter(scriptPath))) { 851 writer.write(cmd); 852 } 853 854 String resultString = ""; 855 try (ParcelFileDescriptor result = getInstrumentation().getUiAutomation(). 856 executeShellCommand(scriptPath); 857 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( 858 new FileInputStream(result.getFileDescriptor())))) { 859 String line; 860 while ((line = bufferedReader.readLine()) != null) { 861 resultString += line + "\n"; 862 } 863 } 864 865 return resultString; 866 } finally { 867 outputFile.delete(); 868 } 869 } catch (IOException e) { 870 throw new AssertionError("Failed to execute shell command: " + cmd, e); 871 } 872 } 873 874 // Get the 'now' timestamp usable with $(adb logcat -v utc -T "time string") getTimeNowForLogcat()875 String getTimeNowForLogcat() { 876 ZonedDateTime utc = ZonedDateTime.now(ZoneOffset.UTC); 877 878 // YYYY-MM-DD hh:mm:ss.mmm 879 return utc.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")); 880 } 881 getLogcatSinceTime(String logcatTime)882 String getLogcatSinceTime(String logcatTime) { 883 // The time has spaces in it but must be passed as a single arg. 884 // Therefore use a temp script file. 885 return executeShellCommandWithTempFile( 886 String.format("logcat -d -v threadtime -v utc -T '%s'", logcatTime)); 887 } 888 889 /** 890 * Toggle iorapd-based readahead and trace-collection. 891 * If iorapd is already enabled and enable is true, does nothing. 892 * If iorapd is already disabled and enable is false, does nothing. 893 */ toggleIorapStatus(boolean enable)894 private void toggleIorapStatus(boolean enable) { 895 boolean currentlyEnabled = false; 896 Log.v(TAG, "toggleIorapStatus " + Boolean.toString(enable)); 897 898 // Do nothing if we are already enabled or disabled. 899 if (mIorapStatus == IorapStatus.ENABLED && enable) { 900 return; 901 } else if (mIorapStatus == IorapStatus.DISABLED && !enable) { 902 return; 903 } 904 905 try { 906 if (!checkIfRoot()) { 907 throw new AssertionError("must be root to toggle iorapd; try adb root?"); 908 } 909 } catch (IOException e) { 910 throw new AssertionError(e); 911 } 912 913 getInstrumentation().getUiAutomation() 914 .executeShellCommand(String.format("setprop iorapd.perfetto.enable %b", enable)); 915 getInstrumentation().getUiAutomation() 916 .executeShellCommand(String.format("setprop iorapd.readahead.enable %b", enable)); 917 getInstrumentation().getUiAutomation() 918 .executeShellCommand(String.format( 919 "setprop iorapd.maintenance.min_traces %d", IORAP_COMPILE_MIN_TRACES)); 920 // this last command blocks until iorapd refreshes its system properties 921 getInstrumentation().getUiAutomation() 922 .executeShellCommand(String.format("dumpsys iorapd --refresh-properties")); 923 924 if (enable) { 925 mIorapStatus = IorapStatus.ENABLED; 926 } else { 927 mIorapStatus = IorapStatus.DISABLED; 928 } 929 } 930 parseArgs(Bundle args)931 private void parseArgs(Bundle args) { 932 mNameToResultKey = new LinkedHashMap<String, String>(); 933 mNameToLaunchTime = new HashMap<>(); 934 String launchIterations = args.getString(KEY_LAUNCH_ITERATIONS); 935 if (launchIterations != null) { 936 mLaunchIterations = Integer.parseInt(launchIterations); 937 } 938 String forceStopApp = args.getString(KEY_FORCE_STOP_APP); 939 940 if (forceStopApp != null) { 941 mForceStopApp = Boolean.parseBoolean(forceStopApp); 942 } 943 944 String enableRecording = args.getString(ENABLE_SCREEN_RECORDING); 945 946 if (enableRecording != null) { 947 mEnableRecording = Boolean.parseBoolean(enableRecording); 948 } 949 String appList = args.getString(KEY_APPS); 950 if (appList == null) 951 return; 952 953 String appNames[] = appList.split("\\|"); 954 for (String pair : appNames) { 955 String[] parts = pair.split("\\^"); 956 if (parts.length != 2) { 957 Log.e(TAG, "The apps key is incorrectly formatted"); 958 fail(); 959 } 960 961 mNameToResultKey.put(parts[0], parts[1]); 962 mNameToLaunchTime.put(parts[0], null); 963 mLastAppName = parts[0]; 964 } 965 String requiredAccounts = args.getString(KEY_REQUIRED_ACCOUNTS); 966 if (requiredAccounts != null) { 967 mRequiredAccounts = new HashSet<String>(); 968 for (String accountType : requiredAccounts.split(",")) { 969 mRequiredAccounts.add(accountType); 970 } 971 } 972 973 String compilerFilterList = args.getString(KEY_COMPILER_FILTERS); 974 if (compilerFilterList != null) { 975 // If a compiler filter is passed, we make a trial launch to force compilation 976 // of the apps. 977 mTrialLaunch = true; 978 mCompilerFilters = compilerFilterList.split("\\|"); 979 } else { 980 // Just pass a null compiler filter to use the current state of the app. 981 mCompilerFilters = new String[1]; 982 } 983 984 String iorapCompilerFilterList = args.getString(KEY_IORAP_COMPILER_FILTERS); 985 if (iorapCompilerFilterList != null) { 986 // Passing in iorap compiler filters implies an iorap trial launch. 987 mIorapTrialLaunch = true; 988 mIorapCompilerFilters = Arrays.asList(iorapCompilerFilterList.split("\\|")); 989 } 990 991 // Pre-populate the results map to avoid null checks. 992 for (String app : mNameToLaunchTime.keySet()) { 993 HashMap<String, List<AppLaunchResult>> map = new HashMap<>(); 994 mNameToLaunchTime.put(app, map); 995 for (String compilerFilter : mCompilerFilters) { 996 map.put(compilerFilter, new ArrayList<>()); 997 } 998 } 999 1000 mTraceDirectoryStr = args.getString(KEY_TRACE_DIRECTORY); 1001 mDropCache = Boolean.parseBoolean(args.getString(KEY_DROP_CACHE)); 1002 mSimplePerfCmd = args.getString(KEY_SIMPLEPERF_CMD); 1003 mLaunchOrder = args.getString(KEY_LAUNCH_ORDER, LAUNCH_ORDER_CYCLIC); 1004 mSimplePerfAppOnly = Boolean.parseBoolean(args.getString(KEY_SIMPLEPERF_APP)); 1005 mCycleCleanUp = Boolean.parseBoolean(args.getString(KEY_CYCLE_CLEAN)); 1006 mTraceAll = Boolean.parseBoolean(args.getString(KEY_TRACE_ALL)); 1007 mTrialLaunch = mTrialLaunch || Boolean.parseBoolean(args.getString(KEY_TRIAL_LAUNCH)); 1008 mIorapTrialLaunch = mIorapTrialLaunch || 1009 Boolean.parseBoolean(args.getString(KEY_IORAP_TRIAL_LAUNCH)); 1010 1011 if (mSimplePerfCmd != null && mSimplePerfAppOnly) { 1012 Log.w(TAG, String.format("Passing both %s and %s is not supported, ignoring %s", 1013 KEY_SIMPLEPERF_CMD, KEY_SIMPLEPERF_APP, KEY_SIMPLEPERF_CMD)); 1014 } 1015 } 1016 hasLeanback(Context context)1017 private boolean hasLeanback(Context context) { 1018 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); 1019 } 1020 createMappings()1021 private void createMappings() { 1022 mNameToIntent = new LinkedHashMap<String, Intent>(); 1023 1024 PackageManager pm = getInstrumentation().getContext() 1025 .getPackageManager(); 1026 Intent intentToResolve = new Intent(Intent.ACTION_MAIN); 1027 intentToResolve.addCategory(hasLeanback(getInstrumentation().getContext()) ? 1028 Intent.CATEGORY_LEANBACK_LAUNCHER : 1029 Intent.CATEGORY_LAUNCHER); 1030 List<ResolveInfo> ris = pm.queryIntentActivities(intentToResolve, 0); 1031 resolveLoop(ris, intentToResolve, pm); 1032 // For Wear 1033 intentToResolve = new Intent(WEARABLE_ACTION_GOOGLE); 1034 ris = pm.queryIntentActivities(intentToResolve, 0); 1035 resolveLoop(ris, intentToResolve, pm); 1036 } 1037 resolveLoop(List<ResolveInfo> ris, Intent intentToResolve, PackageManager pm)1038 private void resolveLoop(List<ResolveInfo> ris, Intent intentToResolve, PackageManager pm) { 1039 if (ris == null || ris.isEmpty()) { 1040 Log.i(TAG, "Could not find any apps"); 1041 } else { 1042 for (ResolveInfo ri : ris) { 1043 Intent startIntent = new Intent(intentToResolve); 1044 startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 1045 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 1046 startIntent.setClassName(ri.activityInfo.packageName, 1047 ri.activityInfo.name); 1048 String appName = ri.loadLabel(pm).toString(); 1049 if (appName != null) { 1050 // Support launching intent using package name or app name 1051 mNameToIntent.put(ri.activityInfo.packageName, startIntent); 1052 mNameToIntent.put(appName, startIntent); 1053 } 1054 } 1055 } 1056 } 1057 startApp(String appName, String launchReason)1058 private AppLaunchResult startApp(String appName, String launchReason) 1059 throws NameNotFoundException, RemoteException { 1060 Log.i(TAG, "Starting " + appName); 1061 if(mEnableRecording) { 1062 startRecording(appName, launchReason); 1063 } 1064 1065 Intent startIntent = mNameToIntent.get(appName); 1066 if (startIntent == null) { 1067 Log.w(TAG, "App does not exist: " + appName); 1068 mResult.putString(mNameToResultKey.get(appName), "App does not exist"); 1069 return new AppLaunchResult(); 1070 } 1071 AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent, launchReason); 1072 Thread t = new Thread(runnable); 1073 t.start(); 1074 try { 1075 t.join(JOIN_TIMEOUT); 1076 } catch (InterruptedException e) { 1077 // ignore 1078 } 1079 1080 if(mEnableRecording) { 1081 stopRecording(); 1082 } 1083 return runnable.getResult(); 1084 } 1085 checkAccountSignIn()1086 private void checkAccountSignIn() { 1087 // ensure that the device has the required account types before starting test 1088 // e.g. device must have a valid Google account sign in to measure a meaningful launch time 1089 // for Gmail 1090 if (mRequiredAccounts == null || mRequiredAccounts.isEmpty()) { 1091 return; 1092 } 1093 final AccountManager am = 1094 (AccountManager) getInstrumentation().getTargetContext().getSystemService( 1095 Context.ACCOUNT_SERVICE); 1096 Account[] accounts = am.getAccounts(); 1097 // use set here in case device has multiple accounts of the same type 1098 Set<String> foundAccounts = new HashSet<String>(); 1099 for (Account account : accounts) { 1100 if (mRequiredAccounts.contains(account.type)) { 1101 foundAccounts.add(account.type); 1102 } 1103 } 1104 // check if account type matches, if not, fail test with message on what account types 1105 // are missing 1106 if (mRequiredAccounts.size() != foundAccounts.size()) { 1107 mRequiredAccounts.removeAll(foundAccounts); 1108 StringBuilder sb = new StringBuilder("Device missing these accounts:"); 1109 for (String account : mRequiredAccounts) { 1110 sb.append(' '); 1111 sb.append(account); 1112 } 1113 fail(sb.toString()); 1114 } 1115 } 1116 startHomeIntent()1117 private void startHomeIntent() { 1118 Intent homeIntent = new Intent(Intent.ACTION_MAIN); 1119 homeIntent.addCategory(Intent.CATEGORY_HOME); 1120 homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 1121 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 1122 getInstrumentation().getContext().startActivity(homeIntent); 1123 sleep(POST_LAUNCH_IDLE_TIMEOUT); 1124 } 1125 cleanUpAfterCycle()1126 private void cleanUpAfterCycle() { 1127 // Kill all the apps 1128 for (String appName : mNameToIntent.keySet()) { 1129 Log.w(TAG, String.format("killing %s", appName)); 1130 forceStopApp(appName); 1131 } 1132 // Drop all the cache. 1133 assertNotNull("Issue in dropping the cache", 1134 getInstrumentation().getUiAutomation() 1135 .executeShellCommand(DROP_CACHE_SCRIPT)); 1136 } 1137 forceStopApp(String appName)1138 private void forceStopApp(String appName) { 1139 Intent startIntent = mNameToIntent.get(appName); 1140 if (startIntent != null) { 1141 String packageName = startIntent.getComponent().getPackageName(); 1142 try { 1143 mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT); 1144 } catch (RemoteException e) { 1145 Log.w(TAG, "Error closing app", e); 1146 } 1147 } 1148 } 1149 killBackgroundApp(String appName)1150 private void killBackgroundApp(String appName) { 1151 Intent startIntent = mNameToIntent.get(appName); 1152 if (startIntent != null) { 1153 String packageName = startIntent.getComponent().getPackageName(); 1154 try { 1155 mAm.killBackgroundProcesses(packageName, UserHandle.USER_CURRENT); 1156 } catch (RemoteException e) { 1157 Log.w(TAG, "Error closing app", e); 1158 } 1159 } 1160 } 1161 sleep(int time)1162 private void sleep(int time) { 1163 try { 1164 Thread.sleep(time); 1165 } catch (InterruptedException e) { 1166 // ignore 1167 } 1168 } 1169 reportError(String appName, String processName)1170 private void reportError(String appName, String processName) { 1171 ActivityManager am = (ActivityManager) getInstrumentation() 1172 .getContext().getSystemService(Context.ACTIVITY_SERVICE); 1173 List<ProcessErrorStateInfo> crashes = am.getProcessesInErrorState(); 1174 if (crashes != null) { 1175 for (ProcessErrorStateInfo crash : crashes) { 1176 if (!crash.processName.equals(processName)) 1177 continue; 1178 1179 Log.w(TAG, appName + " crashed: " + crash.shortMsg); 1180 mResult.putString(mNameToResultKey.get(appName), crash.shortMsg); 1181 return; 1182 } 1183 } 1184 1185 mResult.putString(mNameToResultKey.get(appName), 1186 "Crashed for unknown reason"); 1187 Log.w(TAG, appName 1188 + " not found in process list, most likely it is crashed"); 1189 } 1190 1191 private class LaunchOrder { 1192 private String mApp; 1193 private String mCompilerFilter; 1194 private String mLaunchReason; 1195 private boolean mIorapEnabled; 1196 LaunchOrder(String app, String compilerFilter, String launchReason, boolean iorapEnabled)1197 LaunchOrder(String app, String compilerFilter, String launchReason, boolean iorapEnabled) { 1198 mApp = app; 1199 mCompilerFilter = compilerFilter; 1200 mLaunchReason = launchReason; 1201 mIorapEnabled = iorapEnabled; 1202 } 1203 getApp()1204 public String getApp() { 1205 return mApp; 1206 } 1207 setApp(String app)1208 public void setApp(String app) { 1209 mApp = app; 1210 } 1211 getCompilerFilter()1212 public String getCompilerFilter() { 1213 return mCompilerFilter; 1214 } 1215 getLaunchReason()1216 public String getLaunchReason() { 1217 return mLaunchReason; 1218 } 1219 setLaunchReason(String launchReason)1220 public void setLaunchReason(String launchReason) { 1221 mLaunchReason = launchReason; 1222 } 1223 setIorapEnabled(boolean iorapEnabled)1224 public void setIorapEnabled(boolean iorapEnabled) { 1225 mIorapEnabled = iorapEnabled; 1226 } 1227 getIorapEnabled()1228 public boolean getIorapEnabled() { 1229 return mIorapEnabled; 1230 } 1231 } 1232 1233 private class AppLaunchResult { 1234 long mLaunchTime; 1235 long mCpuCycles; 1236 long mMajorFaults; 1237 AppLaunchResult()1238 AppLaunchResult() { 1239 mLaunchTime = -1L; 1240 mCpuCycles = -1L; 1241 mMajorFaults = -1L; 1242 } 1243 AppLaunchResult(String launchTime, String cpuCycles, String majorFaults)1244 AppLaunchResult(String launchTime, String cpuCycles, String majorFaults) { 1245 try { 1246 mLaunchTime = Long.parseLong(launchTime, 10); 1247 mCpuCycles = Long.parseLong(cpuCycles, 10); 1248 mMajorFaults = Long.parseLong(majorFaults, 10); 1249 } catch (NumberFormatException e) { 1250 Log.e(TAG, "Error parsing result", e); 1251 } 1252 } 1253 } 1254 1255 private class AppLaunchRunnable implements Runnable { 1256 private Intent mLaunchIntent; 1257 private AppLaunchResult mLaunchResult; 1258 private String mLaunchReason; 1259 AppLaunchRunnable(Intent intent, String launchReason)1260 public AppLaunchRunnable(Intent intent, String launchReason) { 1261 mLaunchIntent = intent; 1262 mLaunchReason = launchReason; 1263 mLaunchResult = new AppLaunchResult(); 1264 } 1265 getResult()1266 public AppLaunchResult getResult() { 1267 return mLaunchResult; 1268 } 1269 run()1270 public void run() { 1271 File launchFile = null; 1272 try { 1273 String packageName = mLaunchIntent.getComponent().getPackageName(); 1274 String componentName = mLaunchIntent.getComponent().flattenToShortString(); 1275 if (mForceStopApp) { 1276 mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT); 1277 } 1278 String launchCmd = String.format("%s %s", APP_LAUNCH_CMD, componentName); 1279 if (mSimplePerfAppOnly) { 1280 try { 1281 // executeShellCommand cannot handle shell specific actions, like '&'. 1282 // Therefore, we create a file containing the command and make that 1283 // the command to launch. 1284 launchFile = File.createTempFile(LAUNCH_SCRIPT_NAME, ".sh"); 1285 launchFile.setExecutable(true); 1286 try (FileOutputStream stream = new FileOutputStream(launchFile); 1287 BufferedWriter writer = 1288 new BufferedWriter(new OutputStreamWriter(stream))) { 1289 String cmd = String.format(SIMPLEPERF_APP_CMD, packageName, launchCmd); 1290 // In the file, we need to escape any "$". 1291 cmd = cmd.replace("$", "\\$"); 1292 writer.write(cmd); 1293 } 1294 launchCmd = launchFile.getAbsolutePath(); 1295 } catch (IOException e) { 1296 Log.w(TAG, "Error writing the launch command", e); 1297 return; 1298 } 1299 } else if (null != mSimplePerfCmd) { 1300 launchCmd = String.format("%s %s", mSimplePerfCmd, launchCmd); 1301 } 1302 Log.v(TAG, "Final launch cmd:" + launchCmd); 1303 ParcelFileDescriptor parcelDesc = getInstrumentation().getUiAutomation() 1304 .executeShellCommand(launchCmd); 1305 mLaunchResult = parseLaunchTimeAndWrite(parcelDesc, String.format 1306 ("App Launch :%s %s", componentName, mLaunchReason)); 1307 } catch (RemoteException e) { 1308 Log.w(TAG, "Error launching app", e); 1309 } finally { 1310 if (launchFile != null) { 1311 launchFile.delete(); 1312 } 1313 } 1314 } 1315 1316 /** 1317 * Method to parse the launch time info and write the result to file 1318 * 1319 * @param parcelDesc 1320 * @return 1321 */ parseLaunchTimeAndWrite(ParcelFileDescriptor parcelDesc, String headerInfo)1322 private AppLaunchResult parseLaunchTimeAndWrite(ParcelFileDescriptor parcelDesc, 1323 String headerInfo) { 1324 String launchTime = "-1"; 1325 String cpuCycles = "-1"; 1326 String majorFaults = "-1"; 1327 boolean launchSuccess = false; 1328 try { 1329 InputStream inputStream = new FileInputStream(parcelDesc.getFileDescriptor()); 1330 /* SAMPLE OUTPUT : Cold launch 1331 Starting: Intent { cmp=com.google.android.calculator/com.android.calculator2.Calculator } 1332 Status: ok 1333 LaunchState: COLD 1334 Activity: com.google.android.calculator/com.android.calculator2.Calculator 1335 TotalTime: 357 1336 WaitTime: 377 1337 Complete*/ 1338 /* SAMPLE OUTPUT : Hot launch 1339 Starting: Intent { cmp=com.google.android.calculator/com.android.calculator2.Calculator } 1340 Warning: Activity not started, its current task has been brought to the front 1341 Status: ok 1342 LaunchState: HOT 1343 Activity: com.google.android.calculator/com.android.calculator2.CalculatorGoogle 1344 TotalTime: 60 1345 WaitTime: 67 1346 Complete*/ 1347 /* WITH SIMPLEPERF : 1348 Performance counter statistics, 1349 6595722690,cpu-cycles,4.511040,GHz,(100%), 1350 0,major-faults,0.000,/sec,(100%), 1351 Total test time,1.462129,seconds,*/ 1352 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( 1353 inputStream)); 1354 String line; 1355 mBufferedWriter.newLine(); 1356 mBufferedWriter.write(headerInfo); 1357 mBufferedWriter.newLine(); 1358 while ((line = bufferedReader.readLine()) != null) { 1359 mBufferedWriter.write(line); 1360 mBufferedWriter.newLine(); 1361 if (line.startsWith(SUCCESS_MESSAGE)) { 1362 launchSuccess = true; 1363 } 1364 if (!launchSuccess) { 1365 continue; 1366 } 1367 // Parse TotalTime which is the launch time 1368 if (line.startsWith(TOTAL_TIME_MESSAGE)) { 1369 String launchSplit[] = line.split(":"); 1370 launchTime = launchSplit[1].trim(); 1371 } 1372 1373 if (mSimplePerfAppOnly) { 1374 if (line.contains(",cpu-cycles,")) { 1375 cpuCycles = line.split(",")[0].trim(); 1376 } else if (line.contains(",major-faults,")) { 1377 majorFaults = line.split(",")[0].trim(); 1378 } 1379 } 1380 } 1381 mBufferedWriter.flush(); 1382 inputStream.close(); 1383 } catch (IOException e) { 1384 Log.w(TAG, "Error parsing launch time and writing to file", e); 1385 } 1386 return new AppLaunchResult(launchTime, cpuCycles, majorFaults); 1387 } 1388 1389 } 1390 1391 /** 1392 * Start the screen recording while launching the app. 1393 * 1394 * @param appName 1395 * @param launchReason 1396 */ startRecording(String appName, String launchReason)1397 private void startRecording(String appName, String launchReason) { 1398 Log.v(TAG, "Started Recording"); 1399 mCurrentThread = new RecordingThread("test-screen-record", 1400 String.format("%s_%s", appName, launchReason)); 1401 mCurrentThread.start(); 1402 } 1403 1404 /** 1405 * Stop already started screen recording. 1406 */ stopRecording()1407 private void stopRecording() { 1408 // Skip if not directory. 1409 if (launchSubDir == null) { 1410 return; 1411 } 1412 1413 // Add some extra time to the video end. 1414 SystemClock.sleep(VIDEO_TAIL_BUFFER); 1415 // Ctrl + C all screen record processes. 1416 mCurrentThread.cancel(); 1417 // Wait for the thread to completely die. 1418 try { 1419 mCurrentThread.join(); 1420 } catch (InterruptedException ex) { 1421 Log.e(TAG, "Interrupted when joining the recording thread.", ex); 1422 } 1423 Log.v(TAG, "Stopped Recording"); 1424 } 1425 1426 /** Returns the recording's name for part {@code part} of launch description. */ getOutputFile(String description, int part)1427 private File getOutputFile(String description, int part) { 1428 // Omit the iteration number for the first iteration. 1429 final String fileName = 1430 String.format( 1431 "%s-video%s.mp4", description, part == 1 ? "" : part); 1432 return Paths.get(launchSubDir.getAbsolutePath(), description).toFile(); 1433 } 1434 1435 1436 /** 1437 * Encapsulates the start and stop screen recording logic. 1438 * Copied from ScreenRecordCollector. 1439 */ 1440 private class RecordingThread extends Thread { 1441 private final String mDescription; 1442 private final List<File> mRecordings; 1443 1444 private boolean mContinue; 1445 RecordingThread(String name, String description)1446 public RecordingThread(String name, String description) { 1447 super(name); 1448 1449 mContinue = true; 1450 mRecordings = new ArrayList<>(); 1451 1452 assertNotNull("No test description provided for recording.", description); 1453 mDescription = description; 1454 } 1455 1456 @Override run()1457 public void run() { 1458 try { 1459 // Start at i = 1 to encode parts as X.mp4, X2.mp4, X3.mp4, etc. 1460 for (int i = 1; i <= MAX_RECORDING_PARTS && mContinue; i++) { 1461 File output = getOutputFile(mDescription, i); 1462 Log.d( 1463 TAG, 1464 String.format("Recording screen to %s", output.getAbsolutePath())); 1465 mRecordings.add(output); 1466 // Make sure not to block on this background command in the main thread so 1467 // that the test continues to run, but block in this thread so it does not 1468 // trigger a new screen recording session before the prior one completes. 1469 getDevice().executeShellCommand( 1470 String.format("screenrecord %s", output.getAbsolutePath())); 1471 } 1472 } catch (IOException e) { 1473 throw new RuntimeException("Caught exception while screen recording."); 1474 } 1475 } 1476 cancel()1477 public void cancel() { 1478 mContinue = false; 1479 1480 // Identify the screenrecord PIDs and send SIGINT 2 (Ctrl + C) to each. 1481 try { 1482 String[] pids = getDevice().executeShellCommand( 1483 "pidof screenrecord").split(" "); 1484 for (String pid : pids) { 1485 // Avoid empty process ids, because of weird splitting behavior. 1486 if (pid.isEmpty()) { 1487 continue; 1488 } 1489 1490 getDevice().executeShellCommand( 1491 String.format("kill -2 %s", pid)); 1492 Log.d( 1493 TAG, 1494 String.format("Sent SIGINT 2 to screenrecord process (%s)", pid)); 1495 } 1496 } catch (IOException e) { 1497 throw new RuntimeException("Failed to kill screen recording process."); 1498 } 1499 } 1500 getRecordings()1501 public List<File> getRecordings() { 1502 return mRecordings; 1503 } 1504 } 1505 getDevice()1506 public UiDevice getDevice() { 1507 if (mDevice == null) { 1508 mDevice = UiDevice.getInstance(getInstrumentation()); 1509 } 1510 return mDevice; 1511 } 1512 } 1513