1 /* 2 * Copyright (C) 2019 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.helpers; 18 19 import android.app.UiAutomation; 20 import android.os.ParcelFileDescriptor; 21 import android.util.Log; 22 23 import androidx.test.InstrumentationRegistry; 24 25 import com.android.server.am.nano.MemInfoDumpProto; 26 27 import com.google.protobuf.nano.InvalidProtocolBufferNanoException; 28 29 import java.io.ByteArrayOutputStream; 30 import java.io.FileInputStream; 31 import java.io.IOException; 32 import java.io.InputStream; 33 import java.util.HashMap; 34 import java.util.Map; 35 36 /** 37 * This is a collector helper to use adb "dumpsys meminfo -a --proto" command to get important 38 * memory metrics like PSS, Shared Dirty, Private Dirty, e.t.c. for the specified packages. 39 */ 40 public class DumpsysMeminfoHelper implements ICollectorHelper<Long> { 41 42 private static final String TAG = DumpsysMeminfoHelper.class.getSimpleName(); 43 44 private static final String DUMPSYS_MEMINFO_CMD = "dumpsys meminfo -a --proto %s"; 45 46 private static final String METRIC_SOURCE = "dumpsys"; 47 private static final String METRIC_UNIT = "kb"; 48 49 // The metric names corresponding to the columns in the output of "dumpsys meminfo -a" command 50 private static final String PSS_TOTAL = "pss_total"; 51 private static final String SHARED_DIRTY = "shared_dirty"; 52 private static final String PRIVATE_DIRTY = "private_dirty"; 53 private static final String HEAP_SIZE = "heap_size"; 54 private static final String HEAP_ALLOC = "heap_alloc"; 55 56 // Metric category names, which are the names of the heaps 57 private static final String NATIVE_HEAP = "native"; 58 private static final String DALVIK_HEAP = "dalvik"; 59 private static final String TOTAL_HEAP = "total"; 60 61 private String[] mProcessNames = {}; 62 private UiAutomation mUiAutomation; 63 setUp(String... processNames)64 public void setUp(String... processNames) { 65 if (processNames == null) { 66 return; 67 } 68 mProcessNames = processNames; 69 } 70 71 @Override startCollecting()72 public boolean startCollecting() { 73 mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 74 return true; 75 } 76 77 @Override getMetrics()78 public Map<String, Long> getMetrics() { 79 Map<String, Long> metrics = new HashMap<>(); 80 for (String processName : mProcessNames) { 81 byte[] rawOutput = getRawDumpsysMeminfo(processName); 82 if (rawOutput == null) { 83 Log.e(TAG, "Missing meminfo output for process " + processName); 84 continue; 85 } 86 metrics.putAll(parseMetrics(processName, rawOutput)); 87 } 88 return metrics; 89 } 90 91 @Override stopCollecting()92 public boolean stopCollecting() { 93 return true; 94 } 95 getRawDumpsysMeminfo(String processName)96 private byte[] getRawDumpsysMeminfo(String processName) { 97 if (processName == null || processName.isEmpty()) { 98 return null; 99 } 100 final String cmd = String.format(DUMPSYS_MEMINFO_CMD, processName); 101 ParcelFileDescriptor pfd = mUiAutomation.executeShellCommand(cmd); 102 try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) { 103 return readInputStreamFully(fis); 104 } catch (IOException e) { 105 Log.e(TAG, "Failed to execute command. " + cmd, e); 106 return null; 107 } 108 } 109 parseMetrics(String processName, byte[] rawOutput)110 private Map<String, Long> parseMetrics(String processName, byte[] rawOutput) { 111 Map<String, Long> metrics = new HashMap<>(); 112 try { 113 MemInfoDumpProto memInfo = MemInfoDumpProto.parseFrom(rawOutput); 114 MemInfoDumpProto.ProcessMemory processMemory = findProcessMemory(memInfo, processName); 115 if (processMemory != null) { 116 putHeapMetrics(metrics, processMemory.nativeHeap, NATIVE_HEAP, processName); 117 putHeapMetrics(metrics, processMemory.dalvikHeap, DALVIK_HEAP, processName); 118 putHeapMetric( 119 metrics, 120 processMemory.totalHeap.memInfo.totalPssKb, 121 TOTAL_HEAP, 122 PSS_TOTAL, 123 processName); 124 } 125 } catch (InvalidProtocolBufferNanoException ex) { 126 Log.e(TAG, "Invalid protobuf obtained from `dumpsys meminfo --proto`", ex); 127 } 128 return metrics; 129 } 130 131 /** Find ProcessMemory by name. Looks in app and native process. Returns null on failure. */ findProcessMemory( MemInfoDumpProto memInfo, String processName)132 private static MemInfoDumpProto.ProcessMemory findProcessMemory( 133 MemInfoDumpProto memInfo, String processName) { 134 // Look in app processes first. 135 for (MemInfoDumpProto.AppData appData : memInfo.appProcesses) { 136 if (appData.processMemory == null) { 137 continue; 138 } 139 if (processName.equals(appData.processMemory.processName)) { 140 return appData.processMemory; 141 } 142 } 143 // If not found yet, then look in native processes. 144 for (MemInfoDumpProto.ProcessMemory procMem : memInfo.nativeProcesses) { 145 if (processName.equals(procMem.processName)) { 146 return procMem; 147 } 148 } 149 return null; 150 } 151 putHeapMetrics( Map<String, Long> metrics, MemInfoDumpProto.ProcessMemory.HeapInfo heapInfo, String heapName, String processName)152 private static void putHeapMetrics( 153 Map<String, Long> metrics, 154 MemInfoDumpProto.ProcessMemory.HeapInfo heapInfo, 155 String heapName, 156 String processName) { 157 if (heapInfo == null || heapInfo.memInfo == null) { 158 return; 159 } 160 putHeapMetric(metrics, heapInfo.memInfo.totalPssKb, heapName, PSS_TOTAL, processName); 161 putHeapMetric(metrics, heapInfo.memInfo.sharedDirtyKb, heapName, SHARED_DIRTY, processName); 162 putHeapMetric( 163 metrics, heapInfo.memInfo.privateDirtyKb, heapName, PRIVATE_DIRTY, processName); 164 putHeapMetric(metrics, heapInfo.heapSizeKb, heapName, HEAP_SIZE, processName); 165 putHeapMetric(metrics, heapInfo.heapAllocKb, heapName, HEAP_ALLOC, processName); 166 } 167 putHeapMetric( Map<String, Long> metrics, long value, String heapName, String metricName, String processName)168 private static void putHeapMetric( 169 Map<String, Long> metrics, 170 long value, 171 String heapName, 172 String metricName, 173 String processName) { 174 metrics.put( 175 MetricUtility.constructKey( 176 METRIC_SOURCE, heapName, metricName, METRIC_UNIT, processName), 177 value); 178 } 179 180 /** Copied from {@link com.android.compatibility.common.util.FileUtils#readInputStreamFully}. */ readInputStreamFully(InputStream is)181 private static byte[] readInputStreamFully(InputStream is) { 182 ByteArrayOutputStream os = new ByteArrayOutputStream(); 183 byte[] buffer = new byte[32768]; 184 int count; 185 try { 186 while ((count = is.read(buffer)) != -1) { 187 os.write(buffer, 0, count); 188 } 189 is.close(); 190 } catch (IOException e) { 191 throw new RuntimeException(e); 192 } 193 return os.toByteArray(); 194 } 195 } 196