1 /*
2  * Copyright (C) 2012 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.memoryusage;
17 
18 import android.app.ActivityManager;
19 import android.app.ActivityManager.ProcessErrorStateInfo;
20 import android.app.ActivityManager.RunningAppProcessInfo;
21 import android.app.ActivityTaskManager;
22 import android.app.IActivityManager;
23 import android.app.IActivityTaskManager;
24 import android.app.UiAutomation;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.PackageManager;
28 import android.content.pm.PackageManager.NameNotFoundException;
29 import android.content.pm.ResolveInfo;
30 import android.os.Bundle;
31 import android.os.Debug.MemoryInfo;
32 import android.os.RemoteException;
33 import android.os.UserHandle;
34 import android.test.InstrumentationTestCase;
35 import android.util.Log;
36 
37 import java.util.ArrayList;
38 import java.util.HashMap;
39 import java.util.HashSet;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Set;
43 
44 /**
45  * This test is intended to measure the amount of memory applications use when
46  * they start. Names of the applications are passed in command line, and the
47  * test starts each application, waits until its memory usage is stabilized and
48  * reports the total PSS in kilobytes of each processes.
49  * The instrumentation expects the following key to be passed on the command line:
50  * apps - A list of applications to start and their corresponding result keys
51  * in the following format:
52  * -e apps <app name>^<result key>|<app name>^<result key>
53  */
54 public class MemoryUsageTest extends InstrumentationTestCase {
55 
56     private static final int SLEEP_TIME = 1000;
57     private static final int THRESHOLD = 1024;
58     private static final int MAX_ITERATIONS = 20;
59     private static final int MIN_ITERATIONS = 6;
60     private static final int JOIN_TIMEOUT = 10000;
61 
62     private static final String TAG = "MemoryUsageInstrumentation";
63     private static final String KEY_APPS = "apps";
64     private static final String KEY_PROCS = "persistent";
65     private static final String LAUNCHER_KEY = "launcher";
66     private Map<String, Intent> mNameToIntent;
67     private Map<String, String> mNameToProcess;
68     private Map<String, String> mNameToResultKey;
69     private Set<String> mPersistentProcesses;
70     private IActivityManager mAm;
71     private IActivityTaskManager mAtm;
72 
73     @Override
setUp()74     protected void setUp() throws Exception {
75         super.setUp();
76         getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_0);
77     }
78 
79     @Override
tearDown()80     protected void tearDown() throws Exception {
81         getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_UNFREEZE);
82         super.tearDown();
83     }
84 
testMemory()85     public void testMemory() {
86         MemoryUsageInstrumentation instrumentation =
87                 (MemoryUsageInstrumentation) getInstrumentation();
88         Bundle args = instrumentation.getBundle();
89         mAm = ActivityManager.getService();
90         mAtm = ActivityTaskManager.getService();
91 
92         createMappings();
93         parseArgs(args);
94 
95         Bundle results = new Bundle();
96         for (String app : mNameToResultKey.keySet()) {
97             if (!mPersistentProcesses.contains(app)) {
98                 String processName;
99                 try {
100                     processName = startApp(app);
101                     measureMemory(app, processName, results);
102                     closeApp();
103                 } catch (NameNotFoundException e) {
104                     Log.i(TAG, "Application " + app + " not found");
105                 }
106             } else {
107                 measureMemory(app, app, results);
108             }
109         }
110         instrumentation.sendStatus(0, results);
111     }
112 
getLauncherPackageName()113     private String getLauncherPackageName() {
114       Intent intent = new Intent(Intent.ACTION_MAIN);
115       intent.addCategory(Intent.CATEGORY_HOME);
116       ResolveInfo resolveInfo = getInstrumentation().getContext().
117           getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
118       return resolveInfo.activityInfo.packageName;
119     }
120 
parseListToMap(String list)121     private Map<String, String> parseListToMap(String list) {
122         Map<String, String> map = new HashMap<String, String>();
123         String names[] = list.split("\\|");
124         for (String pair : names) {
125             String[] parts = pair.split("\\^");
126             if (parts.length != 2) {
127                 Log.e(TAG, "The apps key is incorectly formatted");
128                 fail();
129             }
130             map.put(parts[0], parts[1]);
131         }
132         return map;
133     }
134 
parseArgs(Bundle args)135     private void parseArgs(Bundle args) {
136         mNameToResultKey = new HashMap<String, String>();
137         mPersistentProcesses = new HashSet<String>();
138         String appList = args.getString(KEY_APPS);
139         String procList = args.getString(KEY_PROCS);
140         String mLauncherPackageName = getLauncherPackageName();
141         mPersistentProcesses.add(mLauncherPackageName);
142         mNameToResultKey.put(mLauncherPackageName, LAUNCHER_KEY);
143         if (appList == null && procList == null)
144             return;
145         if (appList != null) {
146             mNameToResultKey.putAll(parseListToMap(appList));
147         }
148         if (procList != null) {
149             Map<String, String> procMap = parseListToMap(procList);
150             mPersistentProcesses.addAll(procMap.keySet());
151             mNameToResultKey.putAll(procMap);
152         }
153     }
154 
createMappings()155     private void createMappings() {
156         mNameToIntent = new HashMap<String, Intent>();
157         mNameToProcess = new HashMap<String, String>();
158 
159         PackageManager pm = getInstrumentation().getContext()
160                 .getPackageManager();
161         Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
162         intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
163         List<ResolveInfo> ris = pm.queryIntentActivities(intentToResolve, 0);
164         if (ris == null || ris.isEmpty()) {
165             Log.i(TAG, "Could not find any apps");
166         } else {
167             for (ResolveInfo ri : ris) {
168                 Log.i(TAG, "Name: " + ri.loadLabel(pm).toString()
169                         + " package: " + ri.activityInfo.packageName
170                         + " name: " + ri.activityInfo.name);
171                 Intent startIntent = new Intent(intentToResolve);
172                 startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
173                         | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
174                 startIntent.setClassName(ri.activityInfo.packageName,
175                         ri.activityInfo.name);
176                 mNameToIntent.put(ri.loadLabel(pm).toString(), startIntent);
177                 mNameToProcess.put(ri.loadLabel(pm).toString(),
178                         ri.activityInfo.processName);
179             }
180         }
181     }
182 
startApp(String appName)183     private String startApp(String appName) throws NameNotFoundException {
184         Log.i(TAG, "Starting " + appName);
185 
186         if (!mNameToProcess.containsKey(appName))
187             throw new NameNotFoundException("Could not find: " + appName);
188 
189         String process = mNameToProcess.get(appName);
190         Intent startIntent = mNameToIntent.get(appName);
191 
192         AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent);
193         Thread t = new Thread(runnable);
194         t.start();
195         try {
196             t.join(JOIN_TIMEOUT);
197         } catch (InterruptedException e) {
198             // ignore
199         }
200 
201         return process;
202     }
203 
closeApp()204     private void closeApp() {
205         Intent homeIntent = new Intent(Intent.ACTION_MAIN);
206         homeIntent.addCategory(Intent.CATEGORY_HOME);
207         homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
208                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
209         getInstrumentation().getContext().startActivity(homeIntent);
210         sleep(3000);
211     }
212 
measureMemory(String appName, String processName, Bundle results)213     private void measureMemory(String appName, String processName,
214             Bundle results) {
215         List<Integer> pssData = new ArrayList<Integer>();
216         int pss = 0;
217         int iteration = 0;
218         while (iteration < MAX_ITERATIONS) {
219             sleep(SLEEP_TIME);
220             pss = getPss(processName);
221             Log.i(TAG, appName + "=" + pss);
222             if (pss < 0) {
223                 reportError(appName, processName, results);
224                 return;
225             }
226             pssData.add(pss);
227             if (iteration >= MIN_ITERATIONS && stabilized(pssData)) {
228                 results.putInt(mNameToResultKey.get(appName), pss);
229                 return;
230             }
231             iteration++;
232         }
233 
234         Log.w(TAG, appName + " memory usage did not stabilize");
235         results.putInt(mNameToResultKey.get(appName), average(pssData));
236     }
237 
average(List<Integer> pssData)238     private int average(List<Integer> pssData) {
239         int sum = 0;
240         for (int sample : pssData) {
241             sum += sample;
242         }
243 
244         return sum / pssData.size();
245     }
246 
stabilized(List<Integer> pssData)247     private boolean stabilized(List<Integer> pssData) {
248         if (pssData.size() < 3)
249             return false;
250         int diff1 = Math.abs(pssData.get(pssData.size() - 1) - pssData.get(pssData.size() - 2));
251         int diff2 = Math.abs(pssData.get(pssData.size() - 2) - pssData.get(pssData.size() - 3));
252 
253         Log.i(TAG, "diff1=" + diff1 + " diff2=" + diff2);
254 
255         return (diff1 + diff2) < THRESHOLD;
256     }
257 
sleep(int time)258     private void sleep(int time) {
259         try {
260             Thread.sleep(time);
261         } catch (InterruptedException e) {
262             // ignore
263         }
264     }
265 
reportError(String appName, String processName, Bundle results)266     private void reportError(String appName, String processName, Bundle results) {
267         ActivityManager am = (ActivityManager) getInstrumentation()
268                 .getContext().getSystemService(Context.ACTIVITY_SERVICE);
269         List<ProcessErrorStateInfo> crashes = am.getProcessesInErrorState();
270         if (crashes != null) {
271             for (ProcessErrorStateInfo crash : crashes) {
272                 if (!crash.processName.equals(processName))
273                     continue;
274 
275                 Log.w(TAG, appName + " crashed: " + crash.shortMsg);
276                 results.putString(mNameToResultKey.get(appName), crash.shortMsg);
277                 return;
278             }
279         }
280 
281         results.putString(mNameToResultKey.get(appName),
282                 "Crashed for unknown reason");
283         Log.w(TAG, appName
284                 + " not found in process list, most likely it is crashed");
285     }
286 
getPss(String processName)287     private int getPss(String processName) {
288         ActivityManager am = (ActivityManager) getInstrumentation()
289                 .getContext().getSystemService(Context.ACTIVITY_SERVICE);
290         List<RunningAppProcessInfo> apps = am.getRunningAppProcesses();
291 
292         for (RunningAppProcessInfo proc : apps) {
293             if (!proc.processName.equals(processName)) {
294                 continue;
295             }
296 
297             int[] pids = {
298                     proc.pid };
299 
300             MemoryInfo meminfo = am.getProcessMemoryInfo(pids)[0];
301             return meminfo.getTotalPss();
302 
303         }
304         return -1;
305     }
306 
307     private class AppLaunchRunnable implements Runnable {
308         private Intent mLaunchIntent;
309 
AppLaunchRunnable(Intent intent)310         public AppLaunchRunnable(Intent intent) {
311             mLaunchIntent = intent;
312         }
313 
run()314         public void run() {
315             try {
316                 String mimeType = mLaunchIntent.getType();
317                 if (mimeType == null && mLaunchIntent.getData() != null
318                         && "content".equals(mLaunchIntent.getData().getScheme())) {
319                     mimeType = mAm.getProviderMimeType(mLaunchIntent.getData(),
320                             UserHandle.USER_CURRENT);
321                 }
322 
323                 mAtm.startActivityAndWait(null, null, mLaunchIntent, mimeType,
324                         null, null, 0, mLaunchIntent.getFlags(), null, null,
325                         UserHandle.USER_CURRENT_OR_SELF);
326             } catch (RemoteException e) {
327                 Log.w(TAG, "Error launching app", e);
328             }
329         }
330     }
331 }
332