Skip to content

Commit 88cb79e

Browse files
Maxim SiniavineAndroid (Google) Code Review
authored andcommitted
Merge "Added a test to measure memory usage of apps." into jb-mr1-dev
2 parents 848bde2 + 9229700 commit 88cb79e

File tree

4 files changed

+301
-0
lines changed

4 files changed

+301
-0
lines changed

tests/MemoryUsage/Android.mk

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
LOCAL_PATH:= $(call my-dir)
2+
include $(CLEAR_VARS)
3+
4+
LOCAL_MODULE_TAGS := tests
5+
6+
# Only compile source java files in this apk.
7+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
8+
9+
LOCAL_PACKAGE_NAME := MemoryUsage
10+
11+
LOCAL_SDK_VERSION := 7
12+
13+
include $(BUILD_PACKAGE)
14+
15+
# Use the following include to make our test apk.
16+
include $(call all-makefiles-under,$(LOCAL_PATH))
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
3+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
4+
package="com.android.tests.memoryusage">
5+
<instrumentation android:label="Memory usage instrumentation"
6+
android:name="com.android.tests.memoryusage.MemoryUsageInstrumentation"
7+
android:targetPackage="com.android.tests.memoryusage" />
8+
9+
<application android:label="Memory Usage Test">
10+
<uses-library android:name="android.test.runner" />
11+
</application>
12+
</manifest>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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.os.Bundle;
19+
import android.test.InstrumentationTestRunner;
20+
21+
/**
22+
* InstrumentationTestRunner for use with the {@link MemoryUsageTest}.
23+
*/
24+
public class MemoryUsageInstrumentation extends InstrumentationTestRunner {
25+
26+
private Bundle arguments;
27+
28+
@Override
29+
public void onCreate(Bundle arguments) {
30+
this.arguments = arguments;
31+
super.onCreate(arguments);
32+
}
33+
34+
public Bundle getBundle() {
35+
return arguments;
36+
}
37+
38+
}
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
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.content.Context;
22+
import android.content.Intent;
23+
import android.content.pm.PackageInfo;
24+
import android.content.pm.PackageManager;
25+
import android.content.pm.PackageManager.NameNotFoundException;
26+
import android.content.pm.ResolveInfo;
27+
import android.os.Bundle;
28+
import android.os.Debug.MemoryInfo;
29+
import android.test.InstrumentationTestCase;
30+
import android.util.Log;
31+
32+
import java.util.ArrayList;
33+
import java.util.HashMap;
34+
import java.util.List;
35+
import 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+
*/
47+
public 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> nameToIntent;
58+
private Map<String, String> nameToProcess;
59+
private Map<String, String> nameToResultKey;
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 : nameToResultKey.keySet()) {
71+
String processName;
72+
try {
73+
processName = startApp(app);
74+
measureMemory(app, processName, results);
75+
} catch (NameNotFoundException e) {
76+
Log.i(TAG, "Application " + app + " not found");
77+
}
78+
79+
}
80+
instrumentation.sendStatus(0, results);
81+
}
82+
83+
private void parseArgs(Bundle args) {
84+
nameToResultKey = new HashMap<String, String>();
85+
String appList = args.getString(KEY_APPS);
86+
87+
if (appList == null)
88+
return;
89+
90+
String appNames[] = appList.split("\\|");
91+
for (String pair : appNames) {
92+
String[] parts = pair.split("\\^");
93+
if (parts.length != 2) {
94+
Log.e(TAG, "The apps key is incorectly formatted");
95+
fail();
96+
}
97+
98+
nameToResultKey.put(parts[0], parts[1]);
99+
}
100+
}
101+
102+
private void createMappings() {
103+
nameToIntent = new HashMap<String, Intent>();
104+
nameToProcess = new HashMap<String, String>();
105+
106+
PackageManager pm = getInstrumentation().getContext()
107+
.getPackageManager();
108+
Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
109+
intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
110+
List<ResolveInfo> ris = pm.queryIntentActivities(intentToResolve, 0);
111+
if (ris == null || ris.isEmpty()) {
112+
Log.i(TAG, "Could not find any apps");
113+
} else {
114+
for (ResolveInfo ri : ris) {
115+
Log.i(TAG, "Name: " + ri.loadLabel(pm).toString()
116+
+ " package: " + ri.activityInfo.packageName
117+
+ " name: " + ri.activityInfo.name);
118+
Intent startIntent = new Intent(intentToResolve);
119+
startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
120+
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
121+
startIntent.setClassName(ri.activityInfo.packageName,
122+
ri.activityInfo.name);
123+
nameToIntent.put(ri.loadLabel(pm).toString(), startIntent);
124+
nameToProcess.put(ri.loadLabel(pm).toString(),
125+
ri.activityInfo.processName);
126+
}
127+
}
128+
}
129+
130+
private String startApp(String appName) throws NameNotFoundException {
131+
Log.i(TAG, "Starting " + appName);
132+
133+
if (!nameToProcess.containsKey(appName))
134+
throw new NameNotFoundException("Could not find: " + appName);
135+
136+
String process = nameToProcess.get(appName);
137+
Intent startIntent = nameToIntent.get(appName);
138+
getInstrumentation().getContext().startActivity(startIntent);
139+
return process;
140+
}
141+
142+
private void measureMemory(String appName, String processName,
143+
Bundle results) {
144+
List<Integer> pssData = new ArrayList<Integer>();
145+
int pss = 0;
146+
int iteration = 0;
147+
while (iteration < MAX_ITERATIONS) {
148+
sleep();
149+
pss = getPss(processName);
150+
Log.i(TAG, appName + "=" + pss);
151+
if (pss < 0) {
152+
reportError(appName, processName, results);
153+
return;
154+
}
155+
pssData.add(pss);
156+
if (iteration >= MIN_ITERATIONS && stabilized(pssData)) {
157+
results.putInt(nameToResultKey.get(appName), pss);
158+
return;
159+
}
160+
iteration++;
161+
}
162+
163+
Log.w(TAG, appName + " memory usage did not stabilize");
164+
results.putInt(appName, average(pssData));
165+
}
166+
167+
private int average(List<Integer> pssData) {
168+
int sum = 0;
169+
for (int sample : pssData) {
170+
sum += sample;
171+
}
172+
173+
return sum / pssData.size();
174+
}
175+
176+
private boolean stabilized(List<Integer> pssData) {
177+
if (pssData.size() < 3)
178+
return false;
179+
int diff1 = Math.abs(pssData.get(pssData.size() - 1) - pssData.get(pssData.size() - 2));
180+
int diff2 = Math.abs(pssData.get(pssData.size() - 2) - pssData.get(pssData.size() - 3));
181+
182+
Log.i(TAG, "diff1=" + diff1 + " diff2=" + diff2);
183+
184+
return (diff1 + diff2) < THRESHOLD;
185+
}
186+
187+
private void sleep() {
188+
try {
189+
Thread.sleep(SLEEP_TIME);
190+
} catch (InterruptedException e) {
191+
// ignore
192+
}
193+
}
194+
195+
private void reportError(String appName, String processName, Bundle results) {
196+
ActivityManager am = (ActivityManager) getInstrumentation()
197+
.getContext().getSystemService(Context.ACTIVITY_SERVICE);
198+
List<ProcessErrorStateInfo> crashes = am.getProcessesInErrorState();
199+
if (crashes != null) {
200+
for (ProcessErrorStateInfo crash : crashes) {
201+
if (!crash.processName.equals(processName))
202+
continue;
203+
204+
Log.w(TAG, appName + " crashed: " + crash.shortMsg);
205+
results.putString(nameToResultKey.get(appName), crash.shortMsg);
206+
return;
207+
}
208+
}
209+
210+
results.putString(nameToResultKey.get(appName),
211+
"Crashed for unknown reason");
212+
Log.w(TAG, appName
213+
+ " not found in process list, most likely it is crashed");
214+
}
215+
216+
private int getPss(String processName) {
217+
ActivityManager am = (ActivityManager) getInstrumentation()
218+
.getContext().getSystemService(Context.ACTIVITY_SERVICE);
219+
List<RunningAppProcessInfo> apps = am.getRunningAppProcesses();
220+
221+
for (RunningAppProcessInfo proc : apps) {
222+
if (!proc.processName.equals(processName)) {
223+
continue;
224+
}
225+
226+
int[] pids = {
227+
proc.pid };
228+
229+
MemoryInfo meminfo = am.getProcessMemoryInfo(pids)[0];
230+
return meminfo.getTotalPss();
231+
232+
}
233+
return -1;
234+
}
235+
}

0 commit comments

Comments
 (0)