Home | History | Annotate | Download | only in memoryusage
      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.app.ActivityManagerNative;
     22 import android.app.IActivityManager;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.pm.PackageManager;
     26 import android.content.pm.PackageManager.NameNotFoundException;
     27 import android.content.pm.ResolveInfo;
     28 import android.os.Bundle;
     29 import android.os.Debug.MemoryInfo;
     30 import android.os.RemoteException;
     31 import android.os.UserHandle;
     32 import android.test.InstrumentationTestCase;
     33 import android.util.Log;
     34 
     35 import java.util.ArrayList;
     36 import java.util.HashMap;
     37 import java.util.List;
     38 import 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  */
     50 public 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, 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