/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.tests.memoryusage; import android.app.ActivityManager; import android.app.ActivityManager.ProcessErrorStateInfo; import android.app.ActivityManager.RunningAppProcessInfo; import android.app.ActivityManagerNative; import android.app.IActivityManager; import android.app.UiAutomation; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.os.Bundle; import android.os.Debug.MemoryInfo; import android.os.RemoteException; import android.os.UserHandle; import android.test.InstrumentationTestCase; import android.util.Log; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * This test is intended to measure the amount of memory applications use when * they start. Names of the applications are passed in command line, and the * test starts each application, waits until its memory usage is stabilized and * reports the total PSS in kilobytes of each processes. * The instrumentation expects the following key to be passed on the command line: * apps - A list of applications to start and their corresponding result keys * in the following format: * -e apps ^|^ */ public class MemoryUsageTest extends InstrumentationTestCase { private static final int SLEEP_TIME = 1000; private static final int THRESHOLD = 1024; private static final int MAX_ITERATIONS = 20; private static final int MIN_ITERATIONS = 6; private static final int JOIN_TIMEOUT = 10000; private static final String TAG = "MemoryUsageInstrumentation"; private static final String KEY_APPS = "apps"; private static final String KEY_PROCS = "persistent"; private static final String LAUNCHER_KEY = "launcher"; private Map mNameToIntent; private Map mNameToProcess; private Map mNameToResultKey; private Set mPersistentProcesses; private IActivityManager mAm; @Override protected void setUp() throws Exception { super.setUp(); getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_0); } @Override protected void tearDown() throws Exception { getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_UNFREEZE); super.tearDown(); } public void testMemory() { MemoryUsageInstrumentation instrumentation = (MemoryUsageInstrumentation) getInstrumentation(); Bundle args = instrumentation.getBundle(); mAm = ActivityManagerNative.getDefault(); createMappings(); parseArgs(args); Bundle results = new Bundle(); for (String app : mNameToResultKey.keySet()) { if (!mPersistentProcesses.contains(app)) { String processName; try { processName = startApp(app); measureMemory(app, processName, results); closeApp(); } catch (NameNotFoundException e) { Log.i(TAG, "Application " + app + " not found"); } } else { measureMemory(app, app, results); } } instrumentation.sendStatus(0, results); } private String getLauncherPackageName() { Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_HOME); ResolveInfo resolveInfo = getInstrumentation().getContext(). getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); return resolveInfo.activityInfo.packageName; } private Map parseListToMap(String list) { Map map = new HashMap(); String names[] = list.split("\\|"); for (String pair : names) { String[] parts = pair.split("\\^"); if (parts.length != 2) { Log.e(TAG, "The apps key is incorectly formatted"); fail(); } map.put(parts[0], parts[1]); } return map; } private void parseArgs(Bundle args) { mNameToResultKey = new HashMap(); mPersistentProcesses = new HashSet(); String appList = args.getString(KEY_APPS); String procList = args.getString(KEY_PROCS); String mLauncherPackageName = getLauncherPackageName(); mPersistentProcesses.add(mLauncherPackageName); mNameToResultKey.put(mLauncherPackageName, LAUNCHER_KEY); if (appList == null && procList == null) return; if (appList != null) { mNameToResultKey.putAll(parseListToMap(appList)); } if (procList != null) { Map procMap = parseListToMap(procList); mPersistentProcesses.addAll(procMap.keySet()); mNameToResultKey.putAll(procMap); } } private void createMappings() { mNameToIntent = new HashMap(); mNameToProcess = new HashMap(); PackageManager pm = getInstrumentation().getContext() .getPackageManager(); Intent intentToResolve = new Intent(Intent.ACTION_MAIN); intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER); List ris = pm.queryIntentActivities(intentToResolve, 0); if (ris == null || ris.isEmpty()) { Log.i(TAG, "Could not find any apps"); } else { for (ResolveInfo ri : ris) { Log.i(TAG, "Name: " + ri.loadLabel(pm).toString() + " package: " + ri.activityInfo.packageName + " name: " + ri.activityInfo.name); Intent startIntent = new Intent(intentToResolve); startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); startIntent.setClassName(ri.activityInfo.packageName, ri.activityInfo.name); mNameToIntent.put(ri.loadLabel(pm).toString(), startIntent); mNameToProcess.put(ri.loadLabel(pm).toString(), ri.activityInfo.processName); } } } private String startApp(String appName) throws NameNotFoundException { Log.i(TAG, "Starting " + appName); if (!mNameToProcess.containsKey(appName)) throw new NameNotFoundException("Could not find: " + appName); String process = mNameToProcess.get(appName); Intent startIntent = mNameToIntent.get(appName); AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent); Thread t = new Thread(runnable); t.start(); try { t.join(JOIN_TIMEOUT); } catch (InterruptedException e) { // ignore } return process; } private void closeApp() { Intent homeIntent = new Intent(Intent.ACTION_MAIN); homeIntent.addCategory(Intent.CATEGORY_HOME); homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); getInstrumentation().getContext().startActivity(homeIntent); sleep(3000); } private void measureMemory(String appName, String processName, Bundle results) { List pssData = new ArrayList(); int pss = 0; int iteration = 0; while (iteration < MAX_ITERATIONS) { sleep(SLEEP_TIME); pss = getPss(processName); Log.i(TAG, appName + "=" + pss); if (pss < 0) { reportError(appName, processName, results); return; } pssData.add(pss); if (iteration >= MIN_ITERATIONS && stabilized(pssData)) { results.putInt(mNameToResultKey.get(appName), pss); return; } iteration++; } Log.w(TAG, appName + " memory usage did not stabilize"); results.putInt(mNameToResultKey.get(appName), average(pssData)); } private int average(List pssData) { int sum = 0; for (int sample : pssData) { sum += sample; } return sum / pssData.size(); } private boolean stabilized(List pssData) { if (pssData.size() < 3) return false; int diff1 = Math.abs(pssData.get(pssData.size() - 1) - pssData.get(pssData.size() - 2)); int diff2 = Math.abs(pssData.get(pssData.size() - 2) - pssData.get(pssData.size() - 3)); Log.i(TAG, "diff1=" + diff1 + " diff2=" + diff2); return (diff1 + diff2) < THRESHOLD; } private void sleep(int time) { try { Thread.sleep(time); } catch (InterruptedException e) { // ignore } } private void reportError(String appName, String processName, Bundle results) { ActivityManager am = (ActivityManager) getInstrumentation() .getContext().getSystemService(Context.ACTIVITY_SERVICE); List crashes = am.getProcessesInErrorState(); if (crashes != null) { for (ProcessErrorStateInfo crash : crashes) { if (!crash.processName.equals(processName)) continue; Log.w(TAG, appName + " crashed: " + crash.shortMsg); results.putString(mNameToResultKey.get(appName), crash.shortMsg); return; } } results.putString(mNameToResultKey.get(appName), "Crashed for unknown reason"); Log.w(TAG, appName + " not found in process list, most likely it is crashed"); } private int getPss(String processName) { ActivityManager am = (ActivityManager) getInstrumentation() .getContext().getSystemService(Context.ACTIVITY_SERVICE); List apps = am.getRunningAppProcesses(); for (RunningAppProcessInfo proc : apps) { if (!proc.processName.equals(processName)) { continue; } int[] pids = { proc.pid }; MemoryInfo meminfo = am.getProcessMemoryInfo(pids)[0]; return meminfo.getTotalPss(); } return -1; } private class AppLaunchRunnable implements Runnable { private Intent mLaunchIntent; public AppLaunchRunnable(Intent intent) { mLaunchIntent = intent; } public void run() { try { String mimeType = mLaunchIntent.getType(); if (mimeType == null && mLaunchIntent.getData() != null && "content".equals(mLaunchIntent.getData().getScheme())) { mimeType = mAm.getProviderMimeType(mLaunchIntent.getData(), UserHandle.USER_CURRENT); } mAm.startActivityAndWait(null, null, mLaunchIntent, mimeType, null, null, 0, mLaunchIntent.getFlags(), null, null, UserHandle.USER_CURRENT_OR_SELF); } catch (RemoteException e) { Log.w(TAG, "Error launching app", e); } } } }