Home | History | Annotate | Download | only in tests
      1 /*
      2  * Copyright (C) 2016 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 
     17 package com.android.performance.tests;
     18 
     19 import com.android.tradefed.config.Option;
     20 import com.android.tradefed.device.DeviceNotAvailableException;
     21 import com.android.tradefed.device.ITestDevice;
     22 import com.android.tradefed.log.LogUtil.CLog;
     23 import com.android.tradefed.result.ByteArrayInputStreamSource;
     24 import com.android.tradefed.result.ITestInvocationListener;
     25 import com.android.tradefed.result.LogDataType;
     26 import com.android.tradefed.testtype.IDeviceTest;
     27 import com.android.tradefed.testtype.IRemoteTest;
     28 import com.android.tradefed.util.ProcessInfo;
     29 import com.android.tradefed.util.RunUtil;
     30 import com.android.tradefed.util.StreamUtil;
     31 
     32 import org.junit.Assert;
     33 
     34 import java.util.HashMap;
     35 import java.util.Map;
     36 
     37 /**
     38  * Test to gather post launch memory details after launching app
     39  * that include app memory usage and system memory usage
     40  */
     41 public class HermeticMemoryTest implements IDeviceTest, IRemoteTest {
     42 
     43     private static final String AM_START = "am start -n %s";
     44     private static final String PROC_MEMINFO = "cat /proc/meminfo";
     45     private static final String DUMPSYS_MEMINFO = "dumpsys meminfo -a ";
     46     private static final String MAPS_INFO = "cat /proc/%d/maps";
     47     private static final String SMAPS_INFO = "cat /proc/%d/smaps";
     48     private static final String STATUS_INFO = "cat /proc/%d/status";
     49     private static final String NATIVE_HEAP = "Native";
     50     private static final String DALVIK_HEAP = "Dalvik";
     51     private static final String HEAP = "Heap";
     52     private static final String MEMTOTAL = "MemTotal";
     53     private static final String MEMFREE = "MemFree";
     54     private static final String CACHED = "Cached";
     55     private static final int NO_PROCESS_ID = -1;
     56     private static final String DROP_CACHE = "echo 3 > /proc/sys/vm/drop_caches";
     57 
     58 
     59     @Option(name = "post-app-launch-delay",
     60             description = "The delay, between the app launch and the meminfo dump",
     61             isTimeVal = true)
     62     private long mPostAppLaunchDelay = 60;
     63 
     64     @Option(name = "component-name",
     65             description = "package/activity name to launch the activity")
     66     private String mComponentName = new String();
     67 
     68     @Option(name = "total-memory-kb",
     69             description = "Built in total memory of the device")
     70     private long mTotalMemory = 0;
     71 
     72     @Option(name = "reporting-key", description = "Reporting key is the unique identifier"
     73             + "used to report data in the dashboard.")
     74     private String mRuKey = "";
     75 
     76     private ITestDevice mTestDevice = null;
     77     private ITestInvocationListener mlistener = null;
     78     private Map<String, String> mMetrics = new HashMap<String, String> ();
     79 
     80     @Override
     81     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
     82         mlistener = listener;
     83 
     84         String preMemInfo = mTestDevice.executeShellCommand(PROC_MEMINFO);
     85 
     86         if (!preMemInfo.isEmpty()) {
     87 
     88             uploadLogFile(preMemInfo, "BeforeLaunchProcMemInfo");
     89         } else {
     90             CLog.e("Not able to collect the /proc/meminfo before launching app");
     91         }
     92 
     93         Assert.assertTrue("Device built in memory in kb is mandatory.Use --total-memory-kb value"
     94                 + "command line parameter",
     95                 mTotalMemory != 0);
     96         RunUtil.getDefault().sleep(5000);
     97         mTestDevice.executeShellCommand(DROP_CACHE);
     98         RunUtil.getDefault().sleep(5000);
     99         Assert.assertTrue("Not a valid component name to start the activity",
    100                 (mComponentName.split("/").length == 2));
    101         mTestDevice.executeShellCommand(String.format(AM_START, mComponentName));
    102 
    103         RunUtil.getDefault().sleep(mPostAppLaunchDelay);
    104         String postMemInfo = mTestDevice.executeShellCommand(PROC_MEMINFO);
    105         int processId = getProcessId();
    106         String dumpsysMemInfo = mTestDevice.executeShellCommand(
    107                 String.format("%s %d", DUMPSYS_MEMINFO, processId));
    108         String mapsInfo = mTestDevice.executeShellCommand(
    109                 String.format(MAPS_INFO, processId));
    110         String sMapsInfo = mTestDevice.executeShellCommand(
    111                 String.format(SMAPS_INFO, processId));
    112         String statusInfo = mTestDevice.executeShellCommand(
    113                 String.format(STATUS_INFO, processId));
    114 
    115         if (!postMemInfo.isEmpty()) {
    116             uploadLogFile(postMemInfo, "AfterLaunchProcMemInfo");
    117             parseProcInfo(postMemInfo);
    118         } else {
    119             CLog.e("Not able to collect the proc/meminfo after launching app");
    120         }
    121 
    122         if (NO_PROCESS_ID == processId) {
    123             CLog.e("Process Id not found for the activity launched");
    124         } else {
    125             if (!dumpsysMemInfo.isEmpty()) {
    126                 uploadLogFile(dumpsysMemInfo, "DumpsysMemInfo");
    127                 parseDumpsysInfo(dumpsysMemInfo);
    128             } else {
    129                 CLog.e("Not able to collect the Dumpsys meminfo after launching app");
    130             }
    131             if (!mapsInfo.isEmpty()) {
    132                 uploadLogFile(mapsInfo, "mapsInfo");
    133             } else {
    134                 CLog.e("Not able to collect maps info after launching app");
    135             }
    136             if (!sMapsInfo.isEmpty()) {
    137                 uploadLogFile(sMapsInfo, "smapsInfo");
    138             } else {
    139                 CLog.e("Not able to collect smaps info after launching app");
    140             }
    141             if (!statusInfo.isEmpty()) {
    142                 uploadLogFile(statusInfo, "statusInfo");
    143             } else {
    144                 CLog.e("Not able to collect status info after launching app");
    145             }
    146         }
    147 
    148         reportMetrics(listener, mRuKey, mMetrics);
    149 
    150     }
    151 
    152     /**
    153      * Method to get the process id of the target package/activity name
    154      *
    155      * @return processId of the activity launched
    156      * @throws DeviceNotAvailableException
    157      */
    158     private int getProcessId() throws DeviceNotAvailableException {
    159         String pkgActivitySplit[] = mComponentName.split("/");
    160         if (pkgActivitySplit[0] != null) {
    161             ProcessInfo processData = mTestDevice.getProcessByName(pkgActivitySplit[0]);
    162             if (null != processData) {
    163                 return processData.getPid();
    164             }
    165         }
    166         return NO_PROCESS_ID;
    167     }
    168 
    169     /**
    170      * Method to write the data to test logs.
    171      * @param data
    172      * @param fileName
    173      */
    174     private void uploadLogFile(String data, String fileName) {
    175         ByteArrayInputStreamSource inputStreamSrc = null;
    176         try {
    177             inputStreamSrc = new ByteArrayInputStreamSource(data.getBytes());
    178             mlistener.testLog(fileName, LogDataType.TEXT, inputStreamSrc);
    179         } finally {
    180             StreamUtil.cancel(inputStreamSrc);
    181         }
    182     }
    183 
    184     /**
    185      * Method to parse dalvik and heap info for launched app
    186      */
    187     private void parseDumpsysInfo(String dumpInfo) {
    188         String line[] = dumpInfo.split("\\n");
    189         for (int lineCount = 0; lineCount < line.length; lineCount++) {
    190             String dataSplit[] = line[lineCount].trim().split("\\s+");
    191             if ((dataSplit[0].equalsIgnoreCase(NATIVE_HEAP) && dataSplit[1]
    192                     .equalsIgnoreCase(HEAP)) ||
    193                     (dataSplit[0].equalsIgnoreCase(DALVIK_HEAP) && dataSplit[1]
    194                             .equalsIgnoreCase(HEAP)) ||
    195                     dataSplit[0].equalsIgnoreCase("Total")) {
    196                 if (dataSplit.length > 10) {
    197                     if (dataSplit[0].contains(NATIVE_HEAP)
    198                             || dataSplit[0].contains(DALVIK_HEAP)) {
    199                         mMetrics.put(dataSplit[0] + ":PSS_TOTAL", dataSplit[2]);
    200                         mMetrics.put(dataSplit[0] + ":SHARED_DIRTY", dataSplit[4]);
    201                         mMetrics.put(dataSplit[0] + ":PRIVATE_DIRTY", dataSplit[5]);
    202                         mMetrics.put(dataSplit[0] + ":HEAP_TOTAL", dataSplit[9]);
    203                         mMetrics.put(dataSplit[0] + ":HEAP_ALLOC", dataSplit[10]);
    204                     } else {
    205                         mMetrics.put(dataSplit[0] + ":PSS", dataSplit[1]);
    206                     }
    207                 }
    208             }
    209         }
    210     }
    211 
    212     /**
    213      * Method to parse the system memory details
    214      */
    215     private void parseProcInfo(String memInfo) {
    216         String lineSplit[] = memInfo.split("\\n");
    217         long memTotal = 0;
    218         long memFree = 0;
    219         long cached = 0;
    220         for (int lineCount = 0; lineCount < lineSplit.length; lineCount++) {
    221             String line = lineSplit[lineCount].replace(":", "").trim();
    222             String dataSplit[] = line.split("\\s+");
    223             if (dataSplit[0].equalsIgnoreCase(MEMTOTAL) ||
    224                     dataSplit[0].equalsIgnoreCase(MEMFREE) ||
    225                     dataSplit[0].equalsIgnoreCase(CACHED)) {
    226                 if (dataSplit[0].equalsIgnoreCase(MEMTOTAL)) {
    227                     memTotal = Long.parseLong(dataSplit[1]);
    228                 }
    229                 if (dataSplit[0].equalsIgnoreCase(MEMFREE)) {
    230                     memFree = Long.parseLong(dataSplit[1]);
    231                 }
    232                 if (dataSplit[0].equalsIgnoreCase(CACHED)) {
    233                     cached = Long.parseLong(dataSplit[1]);
    234                 }
    235                 mMetrics.put("System_" + dataSplit[0], dataSplit[1]);
    236             }
    237         }
    238         mMetrics.put("System_Kernal_Firmware", String.valueOf((mTotalMemory - memTotal)));
    239         mMetrics.put("System_Framework_Apps", String.valueOf((memTotal - (memFree + cached))));
    240     }
    241 
    242     /**
    243      * Report run metrics by creating an empty test run to stick them in
    244      *
    245      * @param listener the {@link ITestInvocationListener} of test results
    246      * @param runName the test name
    247      * @param metrics the {@link Map} that contains metrics for the given test
    248      */
    249     void reportMetrics(ITestInvocationListener listener, String runName,
    250             Map<String, String> metrics) {
    251         // Create an empty testRun to report the parsed runMetrics
    252         CLog.d("About to report metrics: %s", metrics);
    253         listener.testRunStarted(runName, 0);
    254         listener.testRunEnded(0, metrics);
    255     }
    256 
    257     @Override
    258     public void setDevice(ITestDevice device) {
    259         mTestDevice = device;
    260     }
    261 
    262     @Override
    263     public ITestDevice getDevice() {
    264         return mTestDevice;
    265     }
    266 }
    267