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