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.tradefed.device.metric; 17 18 import com.android.loganalysis.item.DumpsysProcessMeminfoItem; 19 import com.android.loganalysis.parser.DumpsysProcessMeminfoParser; 20 import com.android.tradefed.config.Option; 21 import com.android.tradefed.device.DeviceNotAvailableException; 22 import com.android.tradefed.device.ITestDevice; 23 import com.android.tradefed.log.LogUtil.CLog; 24 import com.android.tradefed.metrics.proto.MetricMeasurement.DataType; 25 import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements; 26 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; 27 import com.android.tradefed.metrics.proto.MetricMeasurement.NumericValues; 28 29 import java.util.ArrayList; 30 import java.util.Arrays; 31 import java.util.HashMap; 32 import java.util.List; 33 import java.util.Map; 34 import java.util.Map.Entry; 35 36 /** 37 * A {@link ScheduledDeviceMetricCollector} to measure peak memory usage of specified processes. 38 * Collects PSS and USS (private dirty) memory usage values from dumpsys meminfo. The result will be 39 * reported as a test run metric with key in the form of PSS#ProcName[#DeviceNum], in KB. 40 */ 41 public class ProcessMaxMemoryCollector extends ScheduledDeviceMetricCollector { 42 43 @Option( 44 name = "memory-usage-process-name", 45 description = "Process names (from `dumpsys meminfo`) to measure memory usage for" 46 ) 47 private List<String> mProcessNames = new ArrayList<>(); 48 49 private class DeviceMemoryData { 50 /** Peak PSS per process */ 51 private Map<String, Long> mProcPss = new HashMap<>(); 52 /** Peak USS per process */ 53 private Map<String, Long> mProcUss = new HashMap<>(); 54 } 55 56 // Memory usage data per device 57 private Map<ITestDevice, DeviceMemoryData> mMemoryData; 58 private Map<ITestDevice, Map<String, NumericValues.Builder>> mPssMemoryPerProcess; 59 private Map<ITestDevice, Map<String, NumericValues.Builder>> mUssMemoryPerProcess; 60 61 @Override 62 void onStart(DeviceMetricData runData) { 63 mMemoryData = new HashMap<>(); 64 mPssMemoryPerProcess = new HashMap<>(); 65 mUssMemoryPerProcess = new HashMap<>(); 66 67 for (ITestDevice device : getDevices()) { 68 mMemoryData.put(device, new DeviceMemoryData()); 69 mPssMemoryPerProcess.put(device, new HashMap<>()); 70 mUssMemoryPerProcess.put(device, new HashMap<>()); 71 } 72 } 73 74 @Override 75 void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException { 76 try { 77 Map<String, Long> procPss = mMemoryData.get(device).mProcPss; 78 Map<String, Long> procUss = mMemoryData.get(device).mProcUss; 79 for (String proc : mProcessNames) { 80 String dumpResult = device.executeShellCommand("dumpsys meminfo --checkin " + proc); 81 if (dumpResult.startsWith("No process found")) { 82 // process not found, skip 83 continue; 84 } 85 DumpsysProcessMeminfoItem item = 86 new DumpsysProcessMeminfoParser() 87 .parse(Arrays.asList(dumpResult.split("\n"))); 88 Long pss = 89 item.get(DumpsysProcessMeminfoItem.TOTAL) 90 .get(DumpsysProcessMeminfoItem.PSS); 91 Long uss = 92 item.get(DumpsysProcessMeminfoItem.TOTAL) 93 .get(DumpsysProcessMeminfoItem.PRIVATE_DIRTY); 94 if (pss == null || uss == null) { 95 CLog.e("Error parsing meminfo output: " + dumpResult); 96 continue; 97 } 98 99 // Track PSS values 100 if (mPssMemoryPerProcess.get(device) == null) { 101 mPssMemoryPerProcess.put(device, new HashMap<>()); 102 } 103 if (mPssMemoryPerProcess.get(device).get(proc) == null) { 104 mPssMemoryPerProcess.get(device).put(proc, NumericValues.newBuilder()); 105 } 106 mPssMemoryPerProcess.get(device).get(proc).addNumericValue(pss); 107 108 // Track USS values 109 if (mUssMemoryPerProcess.get(device) == null) { 110 mUssMemoryPerProcess.put(device, new HashMap<>()); 111 } 112 if (mUssMemoryPerProcess.get(device).get(proc) == null) { 113 mUssMemoryPerProcess.get(device).put(proc, NumericValues.newBuilder()); 114 } 115 mUssMemoryPerProcess.get(device).get(proc).addNumericValue(uss); 116 117 if (procPss.getOrDefault(proc, 0L) < pss) { 118 procPss.put(proc, pss); 119 } 120 if (procUss.getOrDefault(proc, 0L) < uss) { 121 procUss.put(proc, uss); 122 } 123 } 124 } catch (DeviceNotAvailableException e) { 125 CLog.e(e); 126 } 127 } 128 129 @Override 130 void onEnd(DeviceMetricData runData) { 131 for (ITestDevice device : getDevices()) { 132 // Report all the PSS data for each process 133 for (Entry<String, NumericValues.Builder> values : 134 mPssMemoryPerProcess.get(device).entrySet()) { 135 Metric.Builder metric = Metric.newBuilder(); 136 metric.setMeasurements( 137 Measurements.newBuilder() 138 .setNumericValues(values.getValue().build())) 139 .build(); 140 metric.setUnit("kB").setType(DataType.RAW); 141 runData.addMetricForDevice(device, "PSS#" + values.getKey(), metric); 142 } 143 144 // Report all the USS data for each process 145 for (Entry<String, NumericValues.Builder> values : 146 mUssMemoryPerProcess.get(device).entrySet()) { 147 Metric.Builder metric = Metric.newBuilder(); 148 metric.setMeasurements( 149 Measurements.newBuilder() 150 .setNumericValues(values.getValue().build())) 151 .build(); 152 metric.setUnit("kB").setType(DataType.RAW); 153 runData.addMetricForDevice(device, "USS#" + values.getKey(), metric); 154 } 155 156 // Continue reporting the max PSS / USS for compatibility 157 Map<String, Long> procPss = mMemoryData.get(device).mProcPss; 158 Map<String, Long> procUss = mMemoryData.get(device).mProcUss; 159 for (Entry<String, Long> pss : procPss.entrySet()) { 160 Metric.Builder metric = Metric.newBuilder(); 161 metric.setMeasurements( 162 Measurements.newBuilder().setSingleInt(pss.getValue()).build()); 163 metric.setUnit("kB").setType(DataType.PROCESSED); 164 runData.addMetricForDevice(device, "MAX_PSS#" + pss.getKey(), metric); 165 } 166 for (Entry<String, Long> uss : procUss.entrySet()) { 167 Metric.Builder metric = Metric.newBuilder(); 168 metric.setMeasurements( 169 Measurements.newBuilder().setSingleInt(uss.getValue()).build()); 170 metric.setUnit("kB").setType(DataType.PROCESSED); 171 runData.addMetricForDevice(device, "MAX_USS#" + uss.getKey(), metric); 172 } 173 } 174 } 175 } 176