| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.tests.applaunch; |
| |
| import android.accounts.Account; |
| import android.accounts.AccountManager; |
| import android.app.ActivityManager; |
| import android.app.ActivityManager.ProcessErrorStateInfo; |
| import android.app.IActivityManager; |
| import android.app.UiAutomation; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.PackageManager; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.content.pm.ResolveInfo; |
| import android.os.Bundle; |
| import android.os.ParcelFileDescriptor; |
| import android.os.RemoteException; |
| import android.os.UserHandle; |
| import android.support.test.rule.logging.AtraceLogger; |
| import android.test.InstrumentationTestCase; |
| import android.test.InstrumentationTestRunner; |
| import android.util.Log; |
| |
| import java.io.BufferedReader; |
| import java.io.BufferedWriter; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStreamWriter; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| /** |
| * This test is intended to measure the time it takes for the apps to start. |
| * Names of the applications are passed in command line, and the |
| * test starts each application, and reports the start up time in milliseconds. |
| * The instrumentation expects the following key to be passed on the command line: |
| * apps - A list of applications to start and their corresponding result keys |
| * in the following format: |
| * -e apps <app name>^<result key>|<app name>^<result key> |
| */ |
| public class AppLaunch extends InstrumentationTestCase { |
| |
| private static final int JOIN_TIMEOUT = 10000; |
| private static final String TAG = AppLaunch.class.getSimpleName(); |
| |
| // optional parameter: comma separated list of required account types before proceeding |
| // with the app launch |
| private static final String KEY_REQUIRED_ACCOUNTS = "required_accounts"; |
| private static final String KEY_APPS = "apps"; |
| private static final String KEY_TRIAL_LAUNCH = "trial_launch"; |
| private static final String KEY_LAUNCH_ITERATIONS = "launch_iterations"; |
| private static final String KEY_LAUNCH_ORDER = "launch_order"; |
| private static final String KEY_DROP_CACHE = "drop_cache"; |
| private static final String KEY_SIMPLEPERF_CMD = "simpleperf_cmd"; |
| private static final String KEY_SIMPLEPERF_APP = "simpleperf_app"; |
| private static final String KEY_CYCLE_CLEAN = "cycle_clean"; |
| private static final String KEY_TRACE_ITERATIONS = "trace_iterations"; |
| private static final String KEY_LAUNCH_DIRECTORY = "launch_directory"; |
| private static final String KEY_TRACE_DIRECTORY = "trace_directory"; |
| private static final String KEY_TRACE_CATEGORY = "trace_categories"; |
| private static final String KEY_TRACE_BUFFERSIZE = "trace_bufferSize"; |
| private static final String KEY_TRACE_DUMPINTERVAL = "tracedump_interval"; |
| private static final String KEY_COMPILER_FILTERS = "compiler_filters"; |
| private static final String KEY_FORCE_STOP_APP = "force_stop_app"; |
| |
| private static final String SIMPLEPERF_APP_CMD = |
| "simpleperf --log fatal stat --csv -e cpu-cycles,major-faults --app %s & %s"; |
| private static final String WEARABLE_ACTION_GOOGLE = |
| "com.google.android.wearable.action.GOOGLE"; |
| private static final int INITIAL_LAUNCH_IDLE_TIMEOUT = 5000; // 5s to allow app to idle |
| private static final int POST_LAUNCH_IDLE_TIMEOUT = 750; // 750ms idle for non initial launches |
| private static final int BEFORE_FORCE_STOP_SLEEP_TIMEOUT = 1000; // 1s before force stopping |
| private static final int BEFORE_KILL_APP_SLEEP_TIMEOUT = 1000; // 1s before killing |
| private static final int BETWEEN_LAUNCH_SLEEP_TIMEOUT = 3000; // 3s between launching apps |
| private static final int PROFILE_SAVE_SLEEP_TIMEOUT = 1000; // Allow 1s for the profile to save |
| private static final String LAUNCH_SUB_DIRECTORY = "launch_logs"; |
| private static final String LAUNCH_FILE = "applaunch.txt"; |
| private static final String TRACE_SUB_DIRECTORY = "atrace_logs"; |
| private static final String DEFAULT_TRACE_CATEGORIES = |
| "sched,freq,gfx,view,dalvik,webview,input,wm,disk,am,wm,binder_driver,hal,ss"; |
| private static final String DEFAULT_TRACE_BUFFER_SIZE = "20000"; |
| private static final String DEFAULT_TRACE_DUMP_INTERVAL = "10"; |
| private static final String TRIAL_LAUNCH = "TRIAL_LAUNCH"; |
| private static final String DELIMITER = ","; |
| private static final String DROP_CACHE_SCRIPT = "/data/local/tmp/dropCache.sh"; |
| private static final String APP_LAUNCH_CMD = "am start -W -n"; |
| private static final String SUCCESS_MESSAGE = "Status: ok"; |
| private static final String TOTAL_TIME_MESSAGE = "TotalTime:"; |
| private static final String COMPILE_SUCCESS = "Success"; |
| private static final String LAUNCH_ITERATION = "LAUNCH_ITERATION - %d"; |
| private static final String TRACE_ITERATION = "TRACE_ITERATION-%d"; |
| private static final String LAUNCH_ITERATION_PREFIX = "LAUNCH_ITERATION"; |
| private static final String TRACE_ITERATION_PREFIX = "TRACE_ITERATION"; |
| private static final String LAUNCH_ORDER_CYCLIC = "cyclic"; |
| private static final String LAUNCH_ORDER_SEQUENTIAL = "sequential"; |
| private static final String COMPILE_CMD = "cmd package compile -f -m %s %s"; |
| private static final String SPEED_PROFILE_FILTER = "speed-profile"; |
| private static final String VERIFY_FILTER = "verify"; |
| private static final String LAUNCH_SCRIPT_NAME = "appLaunch"; |
| |
| private Map<String, Intent> mNameToIntent; |
| private List<LaunchOrder> mLaunchOrderList = new ArrayList<LaunchOrder>(); |
| private Map<String, String> mNameToResultKey; |
| private Map<String, Map<String, List<AppLaunchResult>>> mNameToLaunchTime; |
| private IActivityManager mAm; |
| private String mSimplePerfCmd = null; |
| private String mLaunchOrder = null; |
| private boolean mDropCache = false; |
| private int mLaunchIterations = 10; |
| private boolean mForceStopApp = true; |
| private int mTraceLaunchCount = 0; |
| private String mTraceDirectoryStr = null; |
| private Bundle mResult = new Bundle(); |
| private Set<String> mRequiredAccounts; |
| private boolean mTrialLaunch = false; |
| private BufferedWriter mBufferedWriter = null; |
| private boolean mSimplePerfAppOnly = false; |
| private String[] mCompilerFilters = null; |
| private String mLastAppName = ""; |
| private boolean mCycleCleanUp = false; |
| private boolean mIterationCycle = false; |
| private long mCycleTime = 0; |
| private StringBuilder mCycleTimes = new StringBuilder(); |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_0); |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_UNFREEZE); |
| super.tearDown(); |
| } |
| |
| private void addLaunchResult(LaunchOrder launch, AppLaunchResult result) { |
| mNameToLaunchTime.get(launch.getApp()).get(launch.getCompilerFilter()).add(result); |
| } |
| |
| private boolean hasFailureOnFirstLaunch(LaunchOrder launch) { |
| List<AppLaunchResult> results = |
| mNameToLaunchTime.get(launch.getApp()).get(launch.getCompilerFilter()); |
| return (results.size() > 0) && (results.get(0).mLaunchTime < 0); |
| } |
| |
| public void testMeasureStartUpTime() throws RemoteException, NameNotFoundException, |
| IOException, InterruptedException { |
| InstrumentationTestRunner instrumentation = |
| (InstrumentationTestRunner)getInstrumentation(); |
| Bundle args = instrumentation.getArguments(); |
| mAm = ActivityManager.getService(); |
| String launchDirectory = args.getString(KEY_LAUNCH_DIRECTORY); |
| |
| createMappings(); |
| parseArgs(args); |
| checkAccountSignIn(); |
| |
| // Root directory for applaunch file to log the app launch output |
| // Will be useful in case of simpleperf command is used |
| File launchRootDir = null; |
| if (null != launchDirectory && !launchDirectory.isEmpty()) { |
| launchRootDir = new File(launchDirectory); |
| if (!launchRootDir.exists() && !launchRootDir.mkdirs()) { |
| throw new IOException("Unable to create the destination directory"); |
| } |
| } |
| |
| try { |
| File launchSubDir = new File(launchRootDir, LAUNCH_SUB_DIRECTORY); |
| |
| if (!launchSubDir.exists() && !launchSubDir.mkdirs()) { |
| throw new IOException("Unable to create the lauch file sub directory"); |
| } |
| File file = new File(launchSubDir, LAUNCH_FILE); |
| FileOutputStream outputStream = new FileOutputStream(file); |
| mBufferedWriter = new BufferedWriter(new OutputStreamWriter( |
| outputStream)); |
| |
| // Root directory for trace file during the launches |
| File rootTrace = null; |
| File rootTraceSubDir = null; |
| int traceBufferSize = 0; |
| int traceDumpInterval = 0; |
| Set<String> traceCategoriesSet = null; |
| if (null != mTraceDirectoryStr && !mTraceDirectoryStr.isEmpty()) { |
| rootTrace = new File(mTraceDirectoryStr); |
| if (!rootTrace.exists() && !rootTrace.mkdirs()) { |
| throw new IOException("Unable to create the trace directory"); |
| } |
| rootTraceSubDir = new File(rootTrace, TRACE_SUB_DIRECTORY); |
| if (!rootTraceSubDir.exists() && !rootTraceSubDir.mkdirs()) { |
| throw new IOException("Unable to create the trace sub directory"); |
| } |
| assertNotNull("Trace iteration parameter is mandatory", |
| args.getString(KEY_TRACE_ITERATIONS)); |
| mTraceLaunchCount = Integer.parseInt(args.getString(KEY_TRACE_ITERATIONS)); |
| String traceCategoriesStr = args |
| .getString(KEY_TRACE_CATEGORY, DEFAULT_TRACE_CATEGORIES); |
| traceBufferSize = Integer.parseInt(args.getString(KEY_TRACE_BUFFERSIZE, |
| DEFAULT_TRACE_BUFFER_SIZE)); |
| traceDumpInterval = Integer.parseInt(args.getString(KEY_TRACE_DUMPINTERVAL, |
| DEFAULT_TRACE_DUMP_INTERVAL)); |
| traceCategoriesSet = new HashSet<String>(); |
| if (!traceCategoriesStr.isEmpty()) { |
| String[] traceCategoriesSplit = traceCategoriesStr.split(DELIMITER); |
| for (int i = 0; i < traceCategoriesSplit.length; i++) { |
| traceCategoriesSet.add(traceCategoriesSplit[i]); |
| } |
| } |
| } |
| |
| // Get the app launch order based on launch order, trial launch, |
| // launch iterations and trace iterations |
| setLaunchOrder(); |
| |
| for (LaunchOrder launch : mLaunchOrderList) { |
| dropCache(); |
| |
| // App launch times for trial launch will not be used for final |
| // launch time calculations. |
| if (launch.getLaunchReason().equals(TRIAL_LAUNCH)) { |
| mIterationCycle = false; |
| // In the "applaunch.txt" file, trail launches is referenced using |
| // "TRIAL_LAUNCH" |
| Intent startIntent = mNameToIntent.get(launch.getApp()); |
| if (startIntent == null) { |
| Log.w(TAG, "App does not exist: " + launch.getApp()); |
| mResult.putString(mNameToResultKey.get(launch.getApp()), |
| "App does not exist"); |
| continue; |
| } |
| String appPkgName = startIntent.getComponent().getPackageName(); |
| if (SPEED_PROFILE_FILTER.equals(launch.getCompilerFilter())) { |
| assertTrue(String.format("Not able to compile the app : %s", appPkgName), |
| compileApp(VERIFY_FILTER, appPkgName)); |
| } else if (launch.getCompilerFilter() != null) { |
| assertTrue(String.format("Not able to compile the app : %s", appPkgName), |
| compileApp(launch.getCompilerFilter(), appPkgName)); |
| } |
| // We only need to run a trial for the speed-profile filter, but we always |
| // run one for "applaunch.txt" consistency. |
| AppLaunchResult launchResult = |
| startApp(launch.getApp(), launch.getLaunchReason()); |
| if (launchResult.mLaunchTime < 0) { |
| addLaunchResult(launch, new AppLaunchResult()); |
| // simply pass the app if launch isn't successful |
| // error should have already been logged by startApp |
| continue; |
| } |
| sleep(INITIAL_LAUNCH_IDLE_TIMEOUT); |
| if (SPEED_PROFILE_FILTER.equals(launch.getCompilerFilter())) { |
| // Send SIGUSR1 to force dumping a profile. |
| String sendSignalCommand = |
| String.format("killall -s SIGUSR1 %s", appPkgName); |
| getInstrumentation().getUiAutomation().executeShellCommand( |
| sendSignalCommand); |
| // killall is async, wait one second to let the app save the profile. |
| sleep(PROFILE_SAVE_SLEEP_TIMEOUT); |
| assertTrue(String.format("Not able to compile the app : %s", appPkgName), |
| compileApp(launch.getCompilerFilter(), appPkgName)); |
| } |
| } |
| |
| // App launch times used for final calculation |
| else if (launch.getLaunchReason().contains(LAUNCH_ITERATION_PREFIX)) { |
| mIterationCycle = true; |
| AppLaunchResult launchResults = null; |
| if (hasFailureOnFirstLaunch(launch)) { |
| // skip if the app has failures while launched first |
| continue; |
| } |
| // In the "applaunch.txt" file app launches are referenced using |
| // "LAUNCH_ITERATION - ITERATION NUM" |
| launchResults = startApp(launch.getApp(), launch.getLaunchReason()); |
| if (launchResults.mLaunchTime < 0) { |
| addLaunchResult(launch, new AppLaunchResult()); |
| // if it fails once, skip the rest of the launches |
| continue; |
| } else { |
| mCycleTime += launchResults.mLaunchTime; |
| addLaunchResult(launch, launchResults); |
| } |
| sleep(POST_LAUNCH_IDLE_TIMEOUT); |
| } |
| |
| // App launch times for trace launch will not be used for final |
| // launch time calculations. |
| else if (launch.getLaunchReason().contains(TRACE_ITERATION_PREFIX)) { |
| mIterationCycle = false; |
| AtraceLogger atraceLogger = AtraceLogger |
| .getAtraceLoggerInstance(getInstrumentation()); |
| // Start the trace |
| try { |
| atraceLogger.atraceStart(traceCategoriesSet, traceBufferSize, |
| traceDumpInterval, rootTraceSubDir, |
| String.format("%s-%s-%s", launch.getApp(), |
| launch.getCompilerFilter(), launch.getLaunchReason())); |
| startApp(launch.getApp(), launch.getLaunchReason()); |
| sleep(POST_LAUNCH_IDLE_TIMEOUT); |
| } finally { |
| // Stop the trace |
| atraceLogger.atraceStop(); |
| } |
| } |
| if(mForceStopApp) { |
| sleep(BEFORE_FORCE_STOP_SLEEP_TIMEOUT); |
| forceStopApp(launch.getApp()); |
| sleep(BEFORE_KILL_APP_SLEEP_TIMEOUT); |
| // Close again for good measure (just in case). |
| forceStopApp(launch.getApp()); |
| // Kill the backgrounded process in the case forceStopApp only sent it to |
| // background. |
| killBackgroundApp(launch.getApp()); |
| } else { |
| startHomeIntent(); |
| } |
| sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT); |
| |
| // If cycle clean up is enabled and last app launched is |
| // current app then the cycle is completed and eligible for |
| // cleanup. |
| if (LAUNCH_ORDER_CYCLIC.equalsIgnoreCase(mLaunchOrder) && mCycleCleanUp |
| && launch.getApp().equalsIgnoreCase(mLastAppName)) { |
| // Kill all the apps and drop all the cache |
| cleanUpAfterCycle(); |
| if (mIterationCycle) { |
| // Save the previous cycle time and reset the cycle time to 0 |
| mCycleTimes.append(String.format("%d,", mCycleTime)); |
| mCycleTime = 0; |
| } |
| } |
| } |
| } finally { |
| if (null != mBufferedWriter) { |
| mBufferedWriter.close(); |
| } |
| } |
| |
| if (mCycleTimes.length() != 0) { |
| mResult.putString("Cycle_Times", mCycleTimes.toString()); |
| } |
| for (String app : mNameToResultKey.keySet()) { |
| for (String compilerFilter : mCompilerFilters) { |
| StringBuilder launchTimes = new StringBuilder(); |
| StringBuilder cpuCycles = new StringBuilder(); |
| StringBuilder majorFaults = new StringBuilder(); |
| for (AppLaunchResult result : mNameToLaunchTime.get(app).get(compilerFilter)) { |
| launchTimes.append(result.mLaunchTime); |
| launchTimes.append(","); |
| if (mSimplePerfAppOnly) { |
| cpuCycles.append(result.mCpuCycles); |
| cpuCycles.append(","); |
| majorFaults.append(result.mMajorFaults); |
| majorFaults.append(","); |
| } |
| } |
| String filterName = (compilerFilter == null) ? "" : ("-" + compilerFilter); |
| mResult.putString(mNameToResultKey.get(app) + filterName, launchTimes.toString()); |
| if (mSimplePerfAppOnly) { |
| mResult.putString(mNameToResultKey.get(app) + filterName + "-cpuCycles", |
| cpuCycles.toString()); |
| mResult.putString(mNameToResultKey.get(app) + filterName + "-majorFaults", |
| majorFaults.toString()); |
| } |
| } |
| } |
| instrumentation.sendStatus(0, mResult); |
| } |
| |
| /** |
| * Compile the app package using compilerFilter and return true or false |
| * based on status of the compilation command. |
| */ |
| private boolean compileApp(String compilerFilter, String appPkgName) throws IOException { |
| try (ParcelFileDescriptor result = getInstrumentation().getUiAutomation(). |
| executeShellCommand(String.format(COMPILE_CMD, compilerFilter, appPkgName)); |
| BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( |
| new FileInputStream(result.getFileDescriptor())))) { |
| String line; |
| while ((line = bufferedReader.readLine()) != null) { |
| if (line.contains(COMPILE_SUCCESS)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * If launch order is "cyclic" then apps will be launched one after the |
| * other for each iteration count. |
| * If launch order is "sequential" then each app will be launched for given number |
| * iterations at once before launching the other apps. |
| */ |
| private void setLaunchOrder() { |
| if (LAUNCH_ORDER_CYCLIC.equalsIgnoreCase(mLaunchOrder)) { |
| for (String compilerFilter : mCompilerFilters) { |
| if (mTrialLaunch) { |
| for (String app : mNameToResultKey.keySet()) { |
| mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, TRIAL_LAUNCH)); |
| } |
| } |
| for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) { |
| for (String app : mNameToResultKey.keySet()) { |
| mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, |
| String.format(LAUNCH_ITERATION, launchCount))); |
| } |
| } |
| if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) { |
| for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) { |
| for (String app : mNameToResultKey.keySet()) { |
| mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, |
| String.format(TRACE_ITERATION, traceCount))); |
| } |
| } |
| } |
| } |
| } else if (LAUNCH_ORDER_SEQUENTIAL.equalsIgnoreCase(mLaunchOrder)) { |
| for (String compilerFilter : mCompilerFilters) { |
| for (String app : mNameToResultKey.keySet()) { |
| if (mTrialLaunch) { |
| mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, TRIAL_LAUNCH)); |
| } |
| for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) { |
| mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, |
| String.format(LAUNCH_ITERATION, launchCount))); |
| } |
| if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) { |
| for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) { |
| mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, |
| String.format(TRACE_ITERATION, traceCount))); |
| } |
| } |
| } |
| } |
| } else { |
| assertTrue("Launch order is not valid parameter", false); |
| } |
| } |
| |
| private void dropCache() { |
| if (mDropCache) { |
| assertNotNull("Issue in dropping the cache", |
| getInstrumentation().getUiAutomation() |
| .executeShellCommand(DROP_CACHE_SCRIPT)); |
| } |
| } |
| |
| private void parseArgs(Bundle args) { |
| mNameToResultKey = new LinkedHashMap<String, String>(); |
| mNameToLaunchTime = new HashMap<>(); |
| String launchIterations = args.getString(KEY_LAUNCH_ITERATIONS); |
| if (launchIterations != null) { |
| mLaunchIterations = Integer.parseInt(launchIterations); |
| } |
| String forceStopApp = args.getString(KEY_FORCE_STOP_APP); |
| if (forceStopApp != null) { |
| mForceStopApp = Boolean.parseBoolean(forceStopApp); |
| } |
| String appList = args.getString(KEY_APPS); |
| if (appList == null) |
| return; |
| |
| String appNames[] = appList.split("\\|"); |
| for (String pair : appNames) { |
| String[] parts = pair.split("\\^"); |
| if (parts.length != 2) { |
| Log.e(TAG, "The apps key is incorrectly formatted"); |
| fail(); |
| } |
| |
| mNameToResultKey.put(parts[0], parts[1]); |
| mNameToLaunchTime.put(parts[0], null); |
| mLastAppName = parts[0]; |
| } |
| String requiredAccounts = args.getString(KEY_REQUIRED_ACCOUNTS); |
| if (requiredAccounts != null) { |
| mRequiredAccounts = new HashSet<String>(); |
| for (String accountType : requiredAccounts.split(",")) { |
| mRequiredAccounts.add(accountType); |
| } |
| } |
| |
| String compilerFilterList = args.getString(KEY_COMPILER_FILTERS); |
| if (compilerFilterList != null) { |
| // If a compiler filter is passed, we make a trial launch to force compilation |
| // of the apps. |
| mTrialLaunch = true; |
| mCompilerFilters = compilerFilterList.split("\\|"); |
| } else { |
| // Just pass a null compiler filter to use the current state of the app. |
| mCompilerFilters = new String[1]; |
| } |
| |
| // Pre-populate the results map to avoid null checks. |
| for (String app : mNameToLaunchTime.keySet()) { |
| HashMap<String, List<AppLaunchResult>> map = new HashMap<>(); |
| mNameToLaunchTime.put(app, map); |
| for (String compilerFilter : mCompilerFilters) { |
| map.put(compilerFilter, new ArrayList<>()); |
| } |
| } |
| |
| mTraceDirectoryStr = args.getString(KEY_TRACE_DIRECTORY); |
| mDropCache = Boolean.parseBoolean(args.getString(KEY_DROP_CACHE)); |
| mSimplePerfCmd = args.getString(KEY_SIMPLEPERF_CMD); |
| mLaunchOrder = args.getString(KEY_LAUNCH_ORDER, LAUNCH_ORDER_CYCLIC); |
| mSimplePerfAppOnly = Boolean.parseBoolean(args.getString(KEY_SIMPLEPERF_APP)); |
| mCycleCleanUp = Boolean.parseBoolean(args.getString(KEY_CYCLE_CLEAN)); |
| mTrialLaunch = mTrialLaunch || Boolean.parseBoolean(args.getString(KEY_TRIAL_LAUNCH)); |
| |
| if (mSimplePerfCmd != null && mSimplePerfAppOnly) { |
| Log.w(TAG, String.format("Passing both %s and %s is not supported, ignoring %s", |
| KEY_SIMPLEPERF_CMD, KEY_SIMPLEPERF_APP, KEY_SIMPLEPERF_CMD)); |
| } |
| } |
| |
| private boolean hasLeanback(Context context) { |
| return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); |
| } |
| |
| private void createMappings() { |
| mNameToIntent = new LinkedHashMap<String, Intent>(); |
| |
| PackageManager pm = getInstrumentation().getContext() |
| .getPackageManager(); |
| Intent intentToResolve = new Intent(Intent.ACTION_MAIN); |
| intentToResolve.addCategory(hasLeanback(getInstrumentation().getContext()) ? |
| Intent.CATEGORY_LEANBACK_LAUNCHER : |
| Intent.CATEGORY_LAUNCHER); |
| List<ResolveInfo> ris = pm.queryIntentActivities(intentToResolve, 0); |
| resolveLoop(ris, intentToResolve, pm); |
| // For Wear |
| intentToResolve = new Intent(WEARABLE_ACTION_GOOGLE); |
| ris = pm.queryIntentActivities(intentToResolve, 0); |
| resolveLoop(ris, intentToResolve, pm); |
| } |
| |
| private void resolveLoop(List<ResolveInfo> ris, Intent intentToResolve, PackageManager pm) { |
| if (ris == null || ris.isEmpty()) { |
| Log.i(TAG, "Could not find any apps"); |
| } else { |
| for (ResolveInfo ri : ris) { |
| Intent startIntent = new Intent(intentToResolve); |
| startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
| | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); |
| startIntent.setClassName(ri.activityInfo.packageName, |
| ri.activityInfo.name); |
| String appName = ri.loadLabel(pm).toString(); |
| if (appName != null) { |
| // Support launching intent using package name or app name |
| mNameToIntent.put(ri.activityInfo.packageName, startIntent); |
| mNameToIntent.put(appName, startIntent); |
| } |
| } |
| } |
| } |
| |
| private AppLaunchResult startApp(String appName, String launchReason) |
| throws NameNotFoundException, RemoteException { |
| Log.i(TAG, "Starting " + appName); |
| |
| Intent startIntent = mNameToIntent.get(appName); |
| if (startIntent == null) { |
| Log.w(TAG, "App does not exist: " + appName); |
| mResult.putString(mNameToResultKey.get(appName), "App does not exist"); |
| return new AppLaunchResult(); |
| } |
| AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent, launchReason); |
| Thread t = new Thread(runnable); |
| t.start(); |
| try { |
| t.join(JOIN_TIMEOUT); |
| } catch (InterruptedException e) { |
| // ignore |
| } |
| return runnable.getResult(); |
| } |
| |
| private void checkAccountSignIn() { |
| // ensure that the device has the required account types before starting test |
| // e.g. device must have a valid Google account sign in to measure a meaningful launch time |
| // for Gmail |
| if (mRequiredAccounts == null || mRequiredAccounts.isEmpty()) { |
| return; |
| } |
| final AccountManager am = |
| (AccountManager) getInstrumentation().getTargetContext().getSystemService( |
| Context.ACCOUNT_SERVICE); |
| Account[] accounts = am.getAccounts(); |
| // use set here in case device has multiple accounts of the same type |
| Set<String> foundAccounts = new HashSet<String>(); |
| for (Account account : accounts) { |
| if (mRequiredAccounts.contains(account.type)) { |
| foundAccounts.add(account.type); |
| } |
| } |
| // check if account type matches, if not, fail test with message on what account types |
| // are missing |
| if (mRequiredAccounts.size() != foundAccounts.size()) { |
| mRequiredAccounts.removeAll(foundAccounts); |
| StringBuilder sb = new StringBuilder("Device missing these accounts:"); |
| for (String account : mRequiredAccounts) { |
| sb.append(' '); |
| sb.append(account); |
| } |
| fail(sb.toString()); |
| } |
| } |
| |
| private void startHomeIntent() { |
| Intent homeIntent = new Intent(Intent.ACTION_MAIN); |
| homeIntent.addCategory(Intent.CATEGORY_HOME); |
| homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
| | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); |
| getInstrumentation().getContext().startActivity(homeIntent); |
| sleep(POST_LAUNCH_IDLE_TIMEOUT); |
| } |
| |
| private void cleanUpAfterCycle() { |
| // Kill all the apps |
| for (String appName : mNameToIntent.keySet()) { |
| Log.w(TAG, String.format("killing %s", appName)); |
| forceStopApp(appName); |
| } |
| // Drop all the cache. |
| assertNotNull("Issue in dropping the cache", |
| getInstrumentation().getUiAutomation() |
| .executeShellCommand(DROP_CACHE_SCRIPT)); |
| } |
| |
| private void forceStopApp(String appName) { |
| Intent startIntent = mNameToIntent.get(appName); |
| if (startIntent != null) { |
| String packageName = startIntent.getComponent().getPackageName(); |
| try { |
| mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT); |
| } catch (RemoteException e) { |
| Log.w(TAG, "Error closing app", e); |
| } |
| } |
| } |
| |
| private void killBackgroundApp(String appName) { |
| Intent startIntent = mNameToIntent.get(appName); |
| if (startIntent != null) { |
| String packageName = startIntent.getComponent().getPackageName(); |
| try { |
| mAm.killBackgroundProcesses(packageName, UserHandle.USER_CURRENT); |
| } catch (RemoteException e) { |
| Log.w(TAG, "Error closing app", e); |
| } |
| } |
| } |
| |
| private void sleep(int time) { |
| try { |
| Thread.sleep(time); |
| } catch (InterruptedException e) { |
| // ignore |
| } |
| } |
| |
| private void reportError(String appName, String processName) { |
| ActivityManager am = (ActivityManager) getInstrumentation() |
| .getContext().getSystemService(Context.ACTIVITY_SERVICE); |
| List<ProcessErrorStateInfo> crashes = am.getProcessesInErrorState(); |
| if (crashes != null) { |
| for (ProcessErrorStateInfo crash : crashes) { |
| if (!crash.processName.equals(processName)) |
| continue; |
| |
| Log.w(TAG, appName + " crashed: " + crash.shortMsg); |
| mResult.putString(mNameToResultKey.get(appName), crash.shortMsg); |
| return; |
| } |
| } |
| |
| mResult.putString(mNameToResultKey.get(appName), |
| "Crashed for unknown reason"); |
| Log.w(TAG, appName |
| + " not found in process list, most likely it is crashed"); |
| } |
| |
| private class LaunchOrder { |
| private String mApp; |
| private String mCompilerFilter; |
| private String mLaunchReason; |
| |
| LaunchOrder(String app, String compilerFilter, String launchReason){ |
| mApp = app; |
| mCompilerFilter = compilerFilter; |
| mLaunchReason = launchReason; |
| } |
| |
| public String getApp() { |
| return mApp; |
| } |
| |
| public void setApp(String app) { |
| mApp = app; |
| } |
| |
| public String getCompilerFilter() { |
| return mCompilerFilter; |
| } |
| |
| public String getLaunchReason() { |
| return mLaunchReason; |
| } |
| |
| public void setLaunchReason(String launchReason) { |
| mLaunchReason = launchReason; |
| } |
| } |
| |
| private class AppLaunchResult { |
| long mLaunchTime; |
| long mCpuCycles; |
| long mMajorFaults; |
| |
| AppLaunchResult() { |
| mLaunchTime = -1L; |
| mCpuCycles = -1L; |
| mMajorFaults = -1L; |
| } |
| |
| AppLaunchResult(String launchTime, String cpuCycles, String majorFaults) { |
| try { |
| mLaunchTime = Long.parseLong(launchTime, 10); |
| mCpuCycles = Long.parseLong(cpuCycles, 10); |
| mMajorFaults = Long.parseLong(majorFaults, 10); |
| } catch (NumberFormatException e) { |
| Log.e(TAG, "Error parsing result", e); |
| } |
| } |
| } |
| |
| private class AppLaunchRunnable implements Runnable { |
| private Intent mLaunchIntent; |
| private AppLaunchResult mLaunchResult; |
| private String mLaunchReason; |
| |
| public AppLaunchRunnable(Intent intent, String launchReason) { |
| mLaunchIntent = intent; |
| mLaunchReason = launchReason; |
| mLaunchResult = new AppLaunchResult(); |
| } |
| |
| public AppLaunchResult getResult() { |
| return mLaunchResult; |
| } |
| |
| public void run() { |
| File launchFile = null; |
| try { |
| String packageName = mLaunchIntent.getComponent().getPackageName(); |
| String componentName = mLaunchIntent.getComponent().flattenToShortString(); |
| if (mForceStopApp) { |
| mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT); |
| } |
| String launchCmd = String.format("%s %s", APP_LAUNCH_CMD, componentName); |
| if (mSimplePerfAppOnly) { |
| try { |
| // executeShellCommand cannot handle shell specific actions, like '&'. |
| // Therefore, we create a file containing the command and make that |
| // the command to launch. |
| launchFile = File.createTempFile(LAUNCH_SCRIPT_NAME, ".sh"); |
| launchFile.setExecutable(true); |
| try (FileOutputStream stream = new FileOutputStream(launchFile); |
| BufferedWriter writer = |
| new BufferedWriter(new OutputStreamWriter(stream))) { |
| String cmd = String.format(SIMPLEPERF_APP_CMD, packageName, launchCmd); |
| writer.write(cmd); |
| } |
| launchCmd = launchFile.getAbsolutePath(); |
| } catch (IOException e) { |
| Log.w(TAG, "Error writing the launch command", e); |
| return; |
| } |
| } else if (null != mSimplePerfCmd) { |
| launchCmd = String.format("%s %s", mSimplePerfCmd, launchCmd); |
| } |
| Log.v(TAG, "Final launch cmd:" + launchCmd); |
| ParcelFileDescriptor parcelDesc = getInstrumentation().getUiAutomation() |
| .executeShellCommand(launchCmd); |
| mLaunchResult = parseLaunchTimeAndWrite(parcelDesc, String.format |
| ("App Launch :%s %s", componentName, mLaunchReason)); |
| } catch (RemoteException e) { |
| Log.w(TAG, "Error launching app", e); |
| } finally { |
| if (launchFile != null) { |
| launchFile.delete(); |
| } |
| } |
| } |
| |
| /** |
| * Method to parse the launch time info and write the result to file |
| * |
| * @param parcelDesc |
| * @return |
| */ |
| private AppLaunchResult parseLaunchTimeAndWrite(ParcelFileDescriptor parcelDesc, |
| String headerInfo) { |
| String launchTime = "-1"; |
| String cpuCycles = "-1"; |
| String majorFaults = "-1"; |
| boolean launchSuccess = false; |
| try { |
| InputStream inputStream = new FileInputStream(parcelDesc.getFileDescriptor()); |
| /* SAMPLE OUTPUT : Cold launch |
| Starting: Intent { cmp=com.google.android.calculator/com.android.calculator2.Calculator } |
| Status: ok |
| Activity: com.google.android.calculator/com.android.calculator2.Calculator |
| TotalTime: 357 |
| WaitTime: 377 |
| Complete*/ |
| /* SAMPLE OUTPUT : Hot launch |
| Starting: Intent { cmp=com.google.android.calculator/com.android.calculator2.Calculator } |
| Warning: Activity not started, its current task has been brought to the front |
| Status: ok |
| Activity: com.google.android.calculator/com.android.calculator2.CalculatorGoogle |
| TotalTime: 60 |
| WaitTime: 67 |
| Complete*/ |
| /* WITH SIMPLEPERF : |
| Performance counter statistics, |
| 6595722690,cpu-cycles,4.511040,GHz,(100%), |
| 0,major-faults,0.000,/sec,(100%), |
| Total test time,1.462129,seconds,*/ |
| BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( |
| inputStream)); |
| String line; |
| mBufferedWriter.newLine(); |
| mBufferedWriter.write(headerInfo); |
| mBufferedWriter.newLine(); |
| while ((line = bufferedReader.readLine()) != null) { |
| mBufferedWriter.write(line); |
| mBufferedWriter.newLine(); |
| if (line.startsWith(SUCCESS_MESSAGE)) { |
| launchSuccess = true; |
| } |
| if (!launchSuccess) { |
| continue; |
| } |
| // Parse TotalTime which is the launch time |
| if (line.startsWith(TOTAL_TIME_MESSAGE)) { |
| String launchSplit[] = line.split(":"); |
| launchTime = launchSplit[1].trim(); |
| } |
| |
| if (mSimplePerfAppOnly) { |
| if (line.contains(",cpu-cycles,")) { |
| cpuCycles = line.split(",")[0].trim(); |
| } else if (line.contains(",major-faults,")) { |
| majorFaults = line.split(",")[0].trim(); |
| } |
| } |
| } |
| mBufferedWriter.flush(); |
| inputStream.close(); |
| } catch (IOException e) { |
| Log.w(TAG, "Error parsing launch time and writing to file", e); |
| } |
| return new AppLaunchResult(launchTime, cpuCycles, majorFaults); |
| } |
| |
| } |
| } |