1 /* 2 * Copyright (C) 2015 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 17 package com.android.performanceapp.tests; 18 19 import java.io.ByteArrayOutputStream; 20 import java.io.File; 21 import java.io.FileInputStream; 22 import java.io.FileOutputStream; 23 import java.io.IOException; 24 import java.io.InputStream; 25 import java.io.OutputStream; 26 import java.util.ArrayList; 27 import java.util.List; 28 29 import android.app.ActivityManager; 30 import android.app.ActivityManager.RunningAppProcessInfo; 31 import android.content.ComponentName; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.content.pm.ActivityInfo; 35 import android.content.pm.PackageInfo; 36 import android.content.pm.PackageManager.NameNotFoundException; 37 import android.net.Uri; 38 import android.os.Bundle; 39 import android.os.Environment; 40 import android.os.ParcelFileDescriptor; 41 import android.support.test.InstrumentationRegistry; 42 import android.test.InstrumentationTestCase; 43 import android.test.suitebuilder.annotation.MediumTest; 44 import android.util.Log; 45 46 /** 47 * To test the App launch performance on the given target package for the list of activities. It 48 * launches the activities present in the target package the number of times in launch count or it 49 * launches only the activities mentioned in custom activity list the launch count times and returns 50 * the path to the files where the atrace logs are stored corresponding to each activity launch 51 */ 52 public class AppLaunchTests extends InstrumentationTestCase { 53 54 private static final String TAG = "AppLaunchInstrumentation"; 55 private static final String TARGETPACKAGE = "targetpackage"; 56 private static final String SIMPLEPERF_BIN = "simpleperf_bin"; 57 private static final String SIMPLEPERF_EVT = "simpleperf_event"; 58 private static final String SIMPLEPERF_DATA = "simpleperf_data"; 59 private static final String DISPATCHER = "dispatcher"; 60 private static final String ACTIVITYLIST = "activitylist"; 61 private static final String LAUNCHCOUNT = "launchcount"; 62 private static final String RECORDTRACE = "recordtrace"; 63 private static final String ATRACE_CATEGORIES = "tracecategory"; 64 private static final String DEFAULT_CATEGORIES = "am,view,gfx"; 65 private static final String ATRACE_START = "atrace --async_start -b 100000 %s"; 66 private static final String ATRACE_STOP = "atrace --async_stop"; 67 private static final String FORCE_STOP = "am force-stop "; 68 private static final String TARGET_URL = "instanturl"; 69 private static final String URL_PREFIX = "http://"; 70 private static final int BUFFER_SIZE = 8192; 71 72 private Context mContext; 73 private Bundle mResult; 74 private String mTargetPackageName; 75 private String mSimpleperfBin; 76 private String mSimpleperfEvt; 77 private String mSimpleperfDir; 78 private String mAtraceCategory; 79 private String mDispatcher; 80 private int mLaunchCount; 81 private String mCustomActivityList; 82 private String mTargetUrl; 83 private PackageInfo mPackageInfo; 84 private boolean mRecordTrace = true; 85 private List<String> mActivityList = new ArrayList<>(); 86 87 /** 88 * {@inheritDoc} 89 */ 90 @Override setUp()91 public void setUp() throws Exception { 92 super.setUp(); 93 mContext = getInstrumentation().getTargetContext(); 94 assertNotNull("Failed to get context", mContext); 95 Bundle args = InstrumentationRegistry.getArguments(); 96 assertNotNull("Unable to get the args", args); 97 mTargetPackageName = args.getString(TARGETPACKAGE); 98 assertNotNull("Target package name not set", mTargetPackageName); 99 mSimpleperfEvt = args.getString(SIMPLEPERF_EVT); 100 mDispatcher = args.getString(DISPATCHER); 101 102 mAtraceCategory = args.getString(ATRACE_CATEGORIES); 103 if (mAtraceCategory == null || mAtraceCategory.isEmpty()) { 104 mAtraceCategory = DEFAULT_CATEGORIES; 105 } 106 mAtraceCategory = mAtraceCategory.replace(",", " "); 107 108 if (mDispatcher != null && !mDispatcher.isEmpty()) { 109 mSimpleperfBin = args.getString(SIMPLEPERF_BIN); 110 mSimpleperfEvt = args.getString(SIMPLEPERF_EVT); 111 mSimpleperfDir = args.getString(SIMPLEPERF_DATA); 112 } 113 mCustomActivityList = args.getString(ACTIVITYLIST); 114 if (mCustomActivityList == null || mCustomActivityList.isEmpty()) { 115 // Look for instant app configs exist. 116 mTargetUrl = args.getString(TARGET_URL); 117 if (mTargetUrl != null && !mTargetUrl.isEmpty()) { 118 mActivityList.add(args.getString(TARGET_URL)); 119 } else { 120 // Get full list of activities from the target package 121 mActivityList = getActivityList(""); 122 } 123 } else { 124 // Get only the user defined list of activities from the target package 125 mActivityList = getActivityList(mCustomActivityList); 126 } 127 assertTrue("Activity List is empty", (mActivityList.size() > 0)); 128 mLaunchCount = Integer.parseInt(args.getString(LAUNCHCOUNT)); 129 assertTrue("Invalid Launch Count", mLaunchCount > 0); 130 if (args.getString(RECORDTRACE) != null 131 && args.getString(RECORDTRACE).equalsIgnoreCase("false")) { 132 mRecordTrace = false; 133 } 134 mResult = new Bundle(); 135 } 136 137 @MediumTest testAppLaunchPerformance()138 public void testAppLaunchPerformance() throws Exception { 139 assertTrue("Cannot write in External File", isExternalStorageWritable()); 140 File root = Environment.getExternalStorageDirectory(); 141 assertNotNull("Unable to get the root of the external storage", root); 142 File logsDir = new File(root, "atrace_logs"); 143 assertTrue("Unable to create the directory to store atrace logs", logsDir.mkdir()); 144 if (mDispatcher != null && !mDispatcher.isEmpty()) { 145 if (mSimpleperfDir == null) 146 mSimpleperfDir = "/sdcard/perf_simpleperf/"; 147 File simpleperfDir = new File(mSimpleperfDir); 148 assertTrue("Unable to create the directory to store simpleperf data", 149 simpleperfDir.mkdir()); 150 } 151 for (int count = 0; count < mLaunchCount; count++) { 152 for (String activityName : mActivityList) { 153 Intent intent = new Intent(Intent.ACTION_MAIN); 154 if (activityName.startsWith(URL_PREFIX)) { 155 intent = new Intent(Intent.ACTION_VIEW, Uri.parse(activityName)); 156 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 157 } else { 158 ComponentName cn = new ComponentName(mTargetPackageName, 159 mDispatcher != null ? mDispatcher : activityName); 160 intent = new Intent(Intent.ACTION_MAIN); 161 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 162 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); 163 intent.setComponent(cn); 164 165 if (mDispatcher != null) { 166 intent.putExtra("ACTIVITY_NAME", activityName); 167 intent.putExtra("SIMPLEPERF_DIR", mSimpleperfDir); 168 intent.putExtra("SIMPLEPERF_EVT", mSimpleperfEvt); 169 intent.putExtra("SIMPLEPERF_BIN", mSimpleperfBin); 170 } 171 } 172 173 // Start the atrace 174 if (mRecordTrace) { 175 ByteArrayOutputStream startStream = new ByteArrayOutputStream(); 176 try { 177 writeDataToByteStream(getInstrumentation().getUiAutomation() 178 .executeShellCommand(String.format(ATRACE_START, mAtraceCategory)), 179 startStream); 180 } finally { 181 startStream.close(); 182 } 183 184 // Sleep for 5 secs to make sure atrace command is started 185 Thread.sleep(5 * 1000); 186 } 187 188 // Launch the activity 189 mContext.startActivity(intent); 190 Thread.sleep(5 * 1000); 191 192 // Make sure we stops simpleperf 193 if (mDispatcher != null) { 194 try { 195 Runtime.getRuntime().exec("pkill -l SIGINT simpleperf").waitFor(); 196 } catch (Exception e) { 197 Log.v(TAG, "simpleperf throw exception"); 198 e.printStackTrace(); 199 } 200 } 201 202 // Dump atrace info and write it to file 203 if (mRecordTrace) { 204 int processId = getProcessId(mTargetPackageName); 205 assertTrue("Not able to retrive the process id for the package:" 206 + mTargetPackageName, processId > 0); 207 String fileName = new String(); 208 if (!activityName.startsWith(URL_PREFIX)) { 209 fileName = String.format("%s-%d-%d", activityName, count, processId); 210 } else { 211 fileName = String.format("%s-%d-%d", Uri.parse(activityName).getHost(), 212 count, processId); 213 } 214 215 ByteArrayOutputStream stopStream = new ByteArrayOutputStream(); 216 File file = new File(logsDir, fileName); 217 OutputStream fileOutputStream = new FileOutputStream(file); 218 try { 219 writeDataToByteStream( 220 getInstrumentation().getUiAutomation() 221 .executeShellCommand(ATRACE_STOP), 222 stopStream); 223 fileOutputStream.write(stopStream.toByteArray()); 224 } finally { 225 stopStream.close(); 226 fileOutputStream.close(); 227 } 228 229 // To keep track of the activity name,list of atrace file name 230 if (!activityName.startsWith(URL_PREFIX)) { 231 registerTraceFileNames(activityName, fileName); 232 } else { 233 registerTraceFileNames(Uri.parse(activityName).getHost(), fileName); 234 } 235 } 236 237 ByteArrayOutputStream killStream = new ByteArrayOutputStream(); 238 try { 239 writeDataToByteStream(getInstrumentation().getUiAutomation() 240 .executeShellCommand( 241 FORCE_STOP + mTargetPackageName), 242 killStream); 243 } finally { 244 killStream.close(); 245 } 246 247 Thread.sleep(5 * 1000); 248 } 249 } 250 getInstrumentation().sendStatus(0, mResult); 251 } 252 253 /** 254 * Method to check if external storage is writable 255 * @return 256 */ isExternalStorageWritable()257 public boolean isExternalStorageWritable() { 258 return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); 259 } 260 261 /** 262 * Method to get list of activities present in given target package If customActivityList is 263 * passed then include only those activities 264 * @return list of activity names 265 */ getActivityList(String customActivityList)266 private List<String> getActivityList(String customActivityList) { 267 mActivityList = new ArrayList<String>(); 268 try { 269 mPackageInfo = mContext.getPackageManager().getPackageInfo( 270 mTargetPackageName, 1); 271 assertNotNull("Unable to get the target package info", mPackageInfo); 272 } catch (NameNotFoundException e) { 273 fail(String.format("Target application: %s not found", mTargetPackageName)); 274 } 275 for (ActivityInfo activityInfo : mPackageInfo.activities) { 276 mActivityList.add(activityInfo.name); 277 } 278 if (!customActivityList.isEmpty()) { 279 List<String> finalActivityList = new 280 ArrayList<String>(); 281 String customList[] = customActivityList.split(","); 282 for (int count = 0; count < customList.length; count++) { 283 if (mActivityList.contains(customList[count])) { 284 finalActivityList.add(customList[count]); 285 } else { 286 fail(String.format("Activity: %s not present in the target package : %s ", 287 customList[count], mTargetPackageName)); 288 } 289 } 290 mActivityList = finalActivityList; 291 } 292 return mActivityList; 293 } 294 295 /** 296 * Method to retrieve process id from the activity manager 297 * @param processName 298 * @return 299 */ getProcessId(String processName)300 private int getProcessId(String processName) { 301 ActivityManager am = (ActivityManager) getInstrumentation() 302 .getContext().getSystemService(Context.ACTIVITY_SERVICE); 303 List<RunningAppProcessInfo> appsInfo = am.getRunningAppProcesses(); 304 assertNotNull("Unable to retrieve running apps info", appsInfo); 305 for (RunningAppProcessInfo appInfo : appsInfo) { 306 if (appInfo.processName.equals(processName)) { 307 return appInfo.pid; 308 } 309 } 310 return -1; 311 } 312 313 /** 314 * To add the process id to the result map 315 * @param activityNamereturn 316 * @return 317 * @throws IOException 318 */ registerTraceFileNames(String activityName, String absPath)319 private void registerTraceFileNames(String activityName, String absPath) 320 throws IOException { 321 if (mResult.containsKey(activityName)) { 322 String existingResult = (String) mResult.get(activityName); 323 mResult.putString(activityName, existingResult + "," + absPath); 324 } else { 325 mResult.putString(activityName, "" + absPath); 326 } 327 } 328 329 /** 330 * Method to write data into byte array 331 * @param pfDescriptor Used to read the content returned by shell command 332 * @param outputStream Write the data to this output stream read from pfDescriptor 333 * @throws IOException 334 */ writeDataToByteStream( ParcelFileDescriptor pfDescriptor, ByteArrayOutputStream outputStream)335 private void writeDataToByteStream( 336 ParcelFileDescriptor pfDescriptor, ByteArrayOutputStream outputStream) 337 throws IOException { 338 InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfDescriptor); 339 try { 340 byte[] buffer = new byte[BUFFER_SIZE]; 341 int length; 342 while ((length = inputStream.read(buffer)) >= 0) { 343 outputStream.write(buffer, 0, length); 344 } 345 } finally { 346 inputStream.close(); 347 } 348 } 349 350 } 351