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