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
This commit is contained in:
16
tests/MemoryUsage/Android.mk
Normal file
16
tests/MemoryUsage/Android.mk
Normal file
@@ -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))
|
||||
12
tests/MemoryUsage/AndroidManifest.xml
Normal file
12
tests/MemoryUsage/AndroidManifest.xml
Normal file
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user