Home | History | Annotate | Download | only in metric
      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