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