1 /* 2 * Copyright (C) 2018 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.nativeprocesses; 17 18 import com.android.tradefed.device.DeviceNotAvailableException; 19 import com.android.tradefed.device.ITestDevice; 20 import com.android.tradefed.log.LogUtil.CLog; 21 import com.android.tradefed.result.ByteArrayInputStreamSource; 22 import com.android.tradefed.result.LogDataType; 23 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 24 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData; 25 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestMetrics; 26 import com.android.tradefed.testtype.IDeviceTest; 27 28 import org.junit.Rule; 29 import org.junit.Test; 30 import org.junit.runner.RunWith; 31 32 import java.util.ArrayList; 33 import java.util.Arrays; 34 import java.util.HashMap; 35 import java.util.InputMismatchException; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.Optional; 39 import java.util.Scanner; 40 41 /** 42 * Test to gather native processes count and memory usage. 43 * 44 * Native processes are parsed from dumpsys meminfo --oom -c 45 * 46 * <pre> 47 * time,35857009,35857009 48 * oom,native,331721,N/A 49 * proc,native,init,1,2715,N/A,e 50 * proc,native,init,445,1492,N/A,e 51 * ... 52 * </pre> 53 * 54 * For each native process we also look at its showmap output, and gather the PSS, VSS, and RSS. 55 * The showmap output is also saved to test logs. 56 * 57 * The metrics reported are: 58 * 59 * <pre> 60 * - number of native processes 61 * - total memory use of native processes 62 * - memory usage of each native process (one measurement for each process) 63 * </pre> 64 */ 65 @RunWith(DeviceJUnit4ClassRunner.class) 66 public class NativeProcessesMemoryTest implements IDeviceTest { 67 68 @Rule public TestMetrics metrics = new TestMetrics(); 69 @Rule public TestLogData logs = new TestLogData(); 70 71 // the dumpsys process comes and go as we run this test, changing pids, so ignore it 72 private static final List<String> PROCESSES_TO_IGNORE = Arrays.asList("dumpsys"); 73 74 // -c gives us a compact output which is comma separated 75 private static final String DUMPSYS_MEMINFO_OOM_CMD = "dumpsys meminfo --oom -c"; 76 77 private static final String SEPARATOR = ","; 78 private static final String LINE_SEPARATOR = "\\n"; 79 80 // key used to report the number of native processes 81 private static final String NUM_NATIVE_PROCESSES_KEY = "Num_native_processes"; 82 83 // identity for summing over MemoryMetric 84 private final MemoryMetric mZero = new MemoryMetric(0, 0, 0); 85 86 private ITestDevice mTestDevice; 87 88 @Test run()89 public void run() throws DeviceNotAvailableException { 90 // showmap requires root, we enable it here for the rest of the test 91 getDevice().enableAdbRoot(); 92 // process name -> list of pids with that name 93 Map<String, List<String>> nativeProcesses = collectNativeProcesses(); 94 sampleAndLogAllProcesses(nativeProcesses); 95 96 // want to also record the number of native processes 97 metrics.addTestMetric( 98 NUM_NATIVE_PROCESSES_KEY, Integer.toString(nativeProcesses.size())); 99 } 100 101 /** Samples memory of all processes and logs the memory use. */ sampleAndLogAllProcesses(Map<String, List<String>> nativeProcesses)102 private void sampleAndLogAllProcesses(Map<String, List<String>> nativeProcesses) 103 throws DeviceNotAvailableException { 104 for (Map.Entry<String, List<String>> entry : nativeProcesses.entrySet()) { 105 String processName = entry.getKey(); 106 if (PROCESSES_TO_IGNORE.contains(processName)) { 107 continue; 108 } 109 110 // for all pids associated with this process name, record their memory usage 111 List<MemoryMetric> allMemsForProcess = new ArrayList<>(); 112 for (String pid : entry.getValue()) { 113 Optional<MemoryMetric> mem = snapMemoryUsage(processName, pid); 114 if (mem.isPresent()) { 115 allMemsForProcess.add(mem.get()); 116 } 117 } 118 119 // if for some reason a process does not have any MemoryMetric, don't log it 120 if (allMemsForProcess.isEmpty()) { 121 continue; 122 } 123 124 // combine all the memory metrics of process with the same name 125 MemoryMetric combined = allMemsForProcess.stream().reduce(mZero, MemoryMetric::sum); 126 logMemoryMetric(processName, combined); 127 } 128 } 129 130 @Override setDevice(ITestDevice device)131 public void setDevice(ITestDevice device) { 132 mTestDevice = device; 133 } 134 135 @Override getDevice()136 public ITestDevice getDevice() { 137 return mTestDevice; 138 } 139 140 /** 141 * Query system for a list of native process. 142 * 143 * @return a map from process name to a list of pids that share the same name 144 */ collectNativeProcesses()145 private Map<String, List<String>> collectNativeProcesses() throws DeviceNotAvailableException { 146 HashMap<String, List<String>> nativeProcesses = new HashMap<>(); 147 String memInfo = getDevice().executeShellCommand(DUMPSYS_MEMINFO_OOM_CMD); 148 149 for (String line : memInfo.split(LINE_SEPARATOR)) { 150 String[] splits = line.split(SEPARATOR); 151 // ignore lines that don't list a native process 152 if (splits.length < 4 || !splits[0].equals("proc") || !splits[1].equals("native")) { 153 continue; 154 } 155 156 String processName = splits[2]; 157 String pid = splits[3]; 158 if (nativeProcesses.containsKey(processName)) { 159 nativeProcesses.get(processName).add(pid); 160 } else { 161 nativeProcesses.put(processName, new ArrayList<>(Arrays.asList(pid))); 162 } 163 } 164 return nativeProcesses; 165 } 166 167 /** Logs an entire showmap output to the test logs. */ logShowmap(String label, String showmap)168 private void logShowmap(String label, String showmap) { 169 try (ByteArrayInputStreamSource source = 170 new ByteArrayInputStreamSource(showmap.getBytes())) { 171 logs.addTestLog(label + "_showmap", LogDataType.TEXT, source); 172 } 173 } 174 175 /** 176 * Extract VSS, PSS, and RSS from showmap of a process. 177 * The showmap output is also added to test logs. 178 */ snapMemoryUsage(String processName, String pid)179 private Optional<MemoryMetric> snapMemoryUsage(String processName, String pid) 180 throws DeviceNotAvailableException { 181 // TODO(zhin): copied from com.android.tests.sysmem.host.Metrics#sample(), extract? 182 String showmap = getDevice().executeShellCommand("showmap " + pid); 183 logShowmap(processName + "_" + pid, showmap); 184 185 // CHECKSTYLE:OFF Generated code 186 // The last lines of the showmap output looks like: 187 // ------- -------- -------- -------- -------- -------- -------- -------- -------- ---- ------------------------------ 188 // virtual shared shared private private 189 // size RSS PSS clean dirty clean dirty swap swapPSS # object 190 // -------- -------- -------- -------- -------- -------- -------- -------- -------- ---- ------------------------------ 191 // 12848 4240 1543 2852 64 36 1288 0 0 171 TOTAL 192 // CHECKSTYLE:ON Generated code 193 try { 194 int pos = showmap.lastIndexOf("----"); 195 Scanner sc = new Scanner(showmap.substring(pos)); 196 sc.next(); 197 long vss = sc.nextLong(); 198 long rss = sc.nextLong(); 199 long pss = sc.nextLong(); 200 return Optional.of(new MemoryMetric(pss, rss, vss)); 201 } catch (InputMismatchException e) { 202 // this might occur if we have transient processes, it was collected earlier, 203 // but by the time we look at showmap the process is gone 204 CLog.e("Unable to parse MemoryMetric from showmap of pid: " + pid + " processName: " 205 + processName); 206 CLog.e(showmap); 207 return Optional.empty(); 208 } 209 } 210 211 /** Logs a MemoryMetric of a process. */ logMemoryMetric(String processName, MemoryMetric memoryMetric)212 private void logMemoryMetric(String processName, MemoryMetric memoryMetric) { 213 metrics.addTestMetric(processName + "_pss", Long.toString(memoryMetric.pss)); 214 metrics.addTestMetric(processName + "_rss", Long.toString(memoryMetric.rss)); 215 metrics.addTestMetric(processName + "_vss", Long.toString(memoryMetric.vss)); 216 } 217 218 /** Container of memory numbers we want to log. */ 219 private final class MemoryMetric { 220 final long pss; 221 final long rss; 222 final long vss; 223 MemoryMetric(long pss, long rss, long vss)224 MemoryMetric(long pss, long rss, long vss) { 225 this.pss = pss; 226 this.rss = rss; 227 this.vss = vss; 228 } 229 sum(MemoryMetric other)230 MemoryMetric sum(MemoryMetric other) { 231 return new MemoryMetric( 232 pss + other.pss, rss + other.rss, vss + other.vss); 233 } 234 } 235 } 236