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