• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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