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