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