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