diff options
author | Maxim Siniavine <siniavine@google.com> | 2012-08-17 14:17:06 -0700 |
---|---|---|
committer | Maxim Siniavine <siniavine@google.com> | 2012-08-21 11:31:44 -0700 |
commit | 9229700728ec4b7bca28da5325b48a4acb4bfc0d (patch) | |
tree | d09c3effdaf9109e7fff3da26fe474dcce80b710 /tests/MemoryUsage | |
parent | aeca6898041437a7e2ba68c1421b9be90669154d (diff) | |
download | frameworks_base-9229700728ec4b7bca28da5325b48a4acb4bfc0d.zip frameworks_base-9229700728ec4b7bca28da5325b48a4acb4bfc0d.tar.gz frameworks_base-9229700728ec4b7bca28da5325b48a4acb4bfc0d.tar.bz2 |
Added a test to measure memory usage of apps.
Each app uses a certain amount of memory when running in the
foreground. This test takes a list of app on the command line
starts them one at a time and reports the total PSS of the
app's process. The test allows to monitor memory usage over time.
Change-Id: I3411bd96cf7c7af10acbb8deeb9936469b810ea2
Diffstat (limited to 'tests/MemoryUsage')
4 files changed, 301 insertions, 0 deletions
diff --git a/tests/MemoryUsage/Android.mk b/tests/MemoryUsage/Android.mk new file mode 100644 index 0000000..e7bfb4f --- /dev/null +++ b/tests/MemoryUsage/Android.mk @@ -0,0 +1,16 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +# Only compile source java files in this apk. +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := MemoryUsage + +LOCAL_SDK_VERSION := 7 + +include $(BUILD_PACKAGE) + +# Use the following include to make our test apk. +include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file diff --git a/tests/MemoryUsage/AndroidManifest.xml b/tests/MemoryUsage/AndroidManifest.xml new file mode 100644 index 0000000..3932e5b --- /dev/null +++ b/tests/MemoryUsage/AndroidManifest.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.tests.memoryusage"> + <instrumentation android:label="Memory usage instrumentation" + android:name="com.android.tests.memoryusage.MemoryUsageInstrumentation" + android:targetPackage="com.android.tests.memoryusage" /> + + <application android:label="Memory Usage Test"> + <uses-library android:name="android.test.runner" /> + </application> +</manifest>
\ No newline at end of file diff --git a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageInstrumentation.java b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageInstrumentation.java new file mode 100644 index 0000000..ed6d7e6 --- /dev/null +++ b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageInstrumentation.java @@ -0,0 +1,38 @@ +/* + * 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.os.Bundle; +import android.test.InstrumentationTestRunner; + +/** + * InstrumentationTestRunner for use with the {@link MemoryUsageTest}. + */ +public class MemoryUsageInstrumentation extends InstrumentationTestRunner { + + private Bundle arguments; + + @Override + public void onCreate(Bundle arguments) { + this.arguments = arguments; + super.onCreate(arguments); + } + + public Bundle getBundle() { + return arguments; + } + +} diff --git a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java new file mode 100644 index 0000000..f26edc6 --- /dev/null +++ b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java @@ -0,0 +1,235 @@ +/* + * 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.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +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.test.InstrumentationTestCase; +import android.util.Log; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 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 <app name>^<result key>|<app name>^<result key> + */ +public class MemoryUsageTest extends InstrumentationTestCase { + + private static final int SLEEP_TIME = 1000; + private static final int THRESHOLD = 1024; + private static final int MAX_ITERATIONS = 10; + private static final int MIN_ITERATIONS = 4; + + private static final String TAG = "MemoryUsageInstrumentation"; + private static final String KEY_APPS = "apps"; + + private Map<String, Intent> nameToIntent; + private Map<String, String> nameToProcess; + private Map<String, String> nameToResultKey; + + public void testMemory() { + MemoryUsageInstrumentation instrumentation = + (MemoryUsageInstrumentation) getInstrumentation(); + Bundle args = instrumentation.getBundle(); + + createMappings(); + parseArgs(args); + + Bundle results = new Bundle(); + for (String app : nameToResultKey.keySet()) { + String processName; + try { + processName = startApp(app); + measureMemory(app, processName, results); + } catch (NameNotFoundException e) { + Log.i(TAG, "Application " + app + " not found"); + } + + } + instrumentation.sendStatus(0, results); + } + + private void parseArgs(Bundle args) { + nameToResultKey = new HashMap<String, String>(); + String appList = args.getString(KEY_APPS); + + if (appList == null) + return; + + String appNames[] = appList.split("\\|"); + for (String pair : appNames) { + String[] parts = pair.split("\\^"); + if (parts.length != 2) { + Log.e(TAG, "The apps key is incorectly formatted"); + fail(); + } + + nameToResultKey.put(parts[0], parts[1]); + } + } + + private void createMappings() { + nameToIntent = new HashMap<String, Intent>(); + nameToProcess = new HashMap<String, String>(); + + PackageManager pm = getInstrumentation().getContext() + .getPackageManager(); + Intent intentToResolve = new Intent(Intent.ACTION_MAIN); + intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER); + List<ResolveInfo> 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); + nameToIntent.put(ri.loadLabel(pm).toString(), startIntent); + nameToProcess.put(ri.loadLabel(pm).toString(), + ri.activityInfo.processName); + } + } + } + + private String startApp(String appName) throws NameNotFoundException { + Log.i(TAG, "Starting " + appName); + + if (!nameToProcess.containsKey(appName)) + throw new NameNotFoundException("Could not find: " + appName); + + String process = nameToProcess.get(appName); + Intent startIntent = nameToIntent.get(appName); + getInstrumentation().getContext().startActivity(startIntent); + return process; + } + + private void measureMemory(String appName, String processName, + Bundle results) { + List<Integer> pssData = new ArrayList<Integer>(); + int pss = 0; + int iteration = 0; + while (iteration < MAX_ITERATIONS) { + sleep(); + 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(nameToResultKey.get(appName), pss); + return; + } + iteration++; + } + + Log.w(TAG, appName + " memory usage did not stabilize"); + results.putInt(appName, average(pssData)); + } + + private int average(List<Integer> pssData) { + int sum = 0; + for (int sample : pssData) { + sum += sample; + } + + return sum / pssData.size(); + } + + private boolean stabilized(List<Integer> 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() { + try { + Thread.sleep(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<ProcessErrorStateInfo> 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(nameToResultKey.get(appName), crash.shortMsg); + return; + } + } + + results.putString(nameToResultKey.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<RunningAppProcessInfo> 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; + } +} |