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