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 17 package com.android.helpers; 18 19 import android.util.Log; 20 21 import com.android.os.AtomsProto.Atom; 22 import com.android.os.StatsLog.GaugeBucketInfo; 23 import com.android.os.StatsLog.GaugeMetricData; 24 25 import com.google.common.collect.ArrayListMultimap; 26 import com.google.common.collect.ListMultimap; 27 28 import java.io.File; 29 import java.util.ArrayList; 30 import java.util.HashMap; 31 import java.util.List; 32 import java.util.Map; 33 import java.util.regex.Pattern; 34 35 /** 36 * CpuUsageHelper consist of helper methods to set the app 37 * cpu usage configs in statsd to track the cpu usage related 38 * metrics and retrieve the necessary information from statsd 39 * using the config id. 40 */ 41 public class CpuUsageHelper implements ICollectorHelper<Long> { 42 43 private static final String LOG_TAG = CpuUsageHelper.class.getSimpleName(); 44 private static final String CPU_USAGE_PKG_UID = "cpu_usage_pkg_or_uid"; 45 private static final String CPU_USAGE_FREQ = "cpu_usage_freq"; 46 private static final String TOTAL_CPU_USAGE = "total_cpu_usage"; 47 private static final String TOTAL_CPU_USAGE_FREQ = "total_cpu_usage_freq"; 48 private static final String CLUSTER_ID = "cluster"; 49 private static final String FREQ_INDEX = "freq_index"; 50 private static final String USER_TIME = "user_time"; 51 private static final String SYSTEM_TIME = "system_time"; 52 private static final String TOTAL_CPU_TIME = "total_cpu_time"; 53 private static final String CPU_UTILIZATION = "cpu_utilization_average_per_core_percent"; 54 55 private StatsdHelper mStatsdHelper = new StatsdHelper(); 56 private boolean isPerFreqDisabled; 57 private boolean isPerPkgDisabled; 58 private boolean isTotalPkgDisabled; 59 private boolean isTotalFreqDisabled; 60 private boolean isCpuUtilizationEnabled; 61 private long mStartTime; 62 private long mEndTime; 63 private Integer mCpuCores = null; 64 65 @Override startCollecting()66 public boolean startCollecting() { 67 Log.i(LOG_TAG, "Adding CpuUsage config to statsd."); 68 List<Integer> atomIdList = new ArrayList<>(); 69 // Add the atoms to be tracked. 70 atomIdList.add(Atom.CPU_TIME_PER_UID_FIELD_NUMBER); 71 atomIdList.add(Atom.CPU_TIME_PER_FREQ_FIELD_NUMBER); 72 73 if (isCpuUtilizationEnabled) { 74 mStartTime = System.currentTimeMillis(); 75 } 76 77 return mStatsdHelper.addGaugeConfig(atomIdList); 78 } 79 80 @Override getMetrics()81 public Map<String, Long> getMetrics() { 82 Map<String, Long> cpuUsageFinalMap = new HashMap<>(); 83 84 List<GaugeMetricData> gaugeMetricList = mStatsdHelper.getGaugeMetrics(); 85 86 if (isCpuUtilizationEnabled) { 87 mEndTime = System.currentTimeMillis(); 88 } 89 90 ListMultimap<String, Long> cpuUsageMap = ArrayListMultimap.create(); 91 92 for (GaugeMetricData gaugeMetric : gaugeMetricList) { 93 Log.v(LOG_TAG, "Bucket Size: " + gaugeMetric.getBucketInfoCount()); 94 for (GaugeBucketInfo gaugeBucketInfo : gaugeMetric.getBucketInfoList()) { 95 for (Atom atom : gaugeBucketInfo.getAtomList()) { 96 97 // Track CPU usage in user time and system time per package or UID 98 if (atom.getCpuTimePerUid().hasUid()) { 99 int uId = atom.getCpuTimePerUid().getUid(); 100 String packageName = mStatsdHelper.getPackageName(uId); 101 // Convert to milliseconds to compare with CpuTimePerFreq 102 long userTimeMillis = atom.getCpuTimePerUid().getUserTimeMicros() / 1000; 103 long sysTimeMillis = atom.getCpuTimePerUid().getSysTimeMicros() / 1000; 104 Log.v(LOG_TAG, String.format("Uid:%d, Pkg Name: %s, User_Time: %d," 105 + " System_Time: %d", uId, packageName, userTimeMillis, 106 sysTimeMillis)); 107 108 // Use the package name if exist for the UID otherwise use the UID. 109 // Note: UID for the apps will be different across the builds. 110 111 // It is possible to have multiple bucket info. Track all the gauge info 112 // and take the difference of the first and last to compute the 113 // final usage. 114 115 // Add uid suffix to CPU_USAGE_PKG_UID type of key to tell apart processes 116 // with the same package name but different uid in multi-user situations, 117 // so that cpu gauge info won't get mixed up when taking the difference. All 118 // uid suffix will be removed subsequently in final result. 119 120 String UserTimeKey = MetricUtility.constructKey(CPU_USAGE_PKG_UID, 121 (packageName == null) ? String.valueOf(uId) : packageName, 122 USER_TIME, String.valueOf(uId)); 123 String SystemTimeKey = MetricUtility.constructKey(CPU_USAGE_PKG_UID, 124 (packageName == null) ? String.valueOf(uId) : packageName, 125 SYSTEM_TIME, String.valueOf(uId)); 126 cpuUsageMap.put(UserTimeKey, userTimeMillis); 127 cpuUsageMap.put(SystemTimeKey, sysTimeMillis); 128 } 129 130 // Track cpu usage per cluster_id and freq_index 131 if (atom.getCpuTimePerFreq().hasFreqIndex()) { 132 int clusterId = atom.getCpuTimePerFreq().getCluster(); 133 int freqIndex = atom.getCpuTimePerFreq().getFreqIndex(); 134 long timeInFreq = atom.getCpuTimePerFreq().getTimeMillis(); 135 Log.v(LOG_TAG, String.format("Cluster Id: %d FreqIndex: %d," 136 + " Time_in_Freq: %d", clusterId, freqIndex, timeInFreq)); 137 String finalFreqIndexKey = MetricUtility.constructKey( 138 CPU_USAGE_FREQ, CLUSTER_ID, String.valueOf(clusterId), FREQ_INDEX, 139 String.valueOf(freqIndex)); 140 cpuUsageMap.put(finalFreqIndexKey, timeInFreq); 141 } 142 143 } 144 } 145 } 146 147 // Compute the final result map 148 Long totalCpuUsage = 0L; 149 Long totalCpuFreq = 0L; 150 for (String key : cpuUsageMap.keySet()) { 151 List<Long> cpuUsageList = cpuUsageMap.get(key); 152 if (cpuUsageList.size() > 1) { 153 // Compute the total usage by taking the difference of last and first value. 154 Long cpuUsage = cpuUsageList.get(cpuUsageList.size() - 1) 155 - cpuUsageList.get(0); 156 // Add the final result only if the cpu usage is greater than 0. 157 if (cpuUsage > 0) { 158 if (key.startsWith(CPU_USAGE_PKG_UID) 159 && !isPerPkgDisabled) { 160 // remove uid suffix from key. 161 String finalKey = key.substring(0, key.lastIndexOf('_')); 162 if (cpuUsageFinalMap.containsKey(finalKey)) { 163 // accumulate cpu time of the same package from different uid. 164 cpuUsageFinalMap.put( 165 finalKey, cpuUsage + cpuUsageFinalMap.get(finalKey)); 166 } else { 167 cpuUsageFinalMap.put(finalKey, cpuUsage); 168 } 169 } 170 if (key.startsWith(CPU_USAGE_FREQ) 171 && !isPerFreqDisabled) { 172 cpuUsageFinalMap.put(key, cpuUsage); 173 } 174 } 175 // Add the CPU time to their respective (usage or frequency) total metric. 176 if (key.startsWith(CPU_USAGE_PKG_UID) 177 && !isTotalPkgDisabled) { 178 totalCpuUsage += cpuUsage; 179 } else if (key.startsWith(CPU_USAGE_FREQ) 180 && !isTotalFreqDisabled) { 181 totalCpuFreq += cpuUsage; 182 } 183 } 184 } 185 // Put the total results into the final result map. 186 if (!isTotalPkgDisabled) { 187 cpuUsageFinalMap.put(TOTAL_CPU_USAGE, totalCpuUsage); 188 } 189 if (!isTotalFreqDisabled) { 190 cpuUsageFinalMap.put(TOTAL_CPU_USAGE_FREQ, totalCpuFreq); 191 } 192 193 // Calculate cpu utilization 194 if (isCpuUtilizationEnabled) { 195 long totalCpuTime = (mEndTime - mStartTime) * getCores(); 196 cpuUsageFinalMap.put(TOTAL_CPU_TIME, totalCpuTime); 197 if (!isTotalPkgDisabled) { 198 double utilization = 199 ((double) cpuUsageFinalMap.get(TOTAL_CPU_USAGE) / totalCpuTime) * 100; 200 cpuUsageFinalMap.put(CPU_UTILIZATION, (long) utilization); 201 } 202 } 203 204 return cpuUsageFinalMap; 205 } 206 207 /** 208 * Remove the statsd config used to track the cpu usage metrics. 209 */ 210 @Override stopCollecting()211 public boolean stopCollecting() { 212 return mStatsdHelper.removeStatsConfig(); 213 } 214 215 /** 216 * Disable the cpu metric collection per package. 217 */ setDisablePerPackage()218 public void setDisablePerPackage() { 219 isPerPkgDisabled = true; 220 } 221 222 /** 223 * Disable the cpu metric collection per frequency. 224 */ setDisablePerFrequency()225 public void setDisablePerFrequency() { 226 isPerFreqDisabled = true; 227 } 228 229 /** 230 * Disable the total cpu metric collection by all the packages. 231 */ setDisableTotalPackage()232 public void setDisableTotalPackage() { 233 isTotalPkgDisabled = true; 234 } 235 236 /** 237 * Disable the total cpu metric collection by all the frequency. 238 */ setDisableTotalFrequency()239 public void setDisableTotalFrequency() { 240 isTotalFreqDisabled = true; 241 } 242 243 /** 244 * Enable the collection of cpu utilization. 245 */ setEnableCpuUtilization()246 public void setEnableCpuUtilization() { 247 isCpuUtilizationEnabled = true; 248 } 249 250 /** 251 * return the number of cores that the device has. 252 */ getCores()253 private int getCores() { 254 if (mCpuCores == null) { 255 int count = 0; 256 // every core corresponds to a folder named "cpu[0-9]+" under /sys/devices/system/cpu 257 File cpuDir = new File("/sys/devices/system/cpu"); 258 File[] files = cpuDir.listFiles(); 259 for (File file : files) { 260 if (Pattern.matches("cpu[0-9]+", file.getName())) { 261 ++count; 262 } 263 } 264 mCpuCores = count; 265 } 266 return mCpuCores.intValue(); 267 } 268 } 269