blob: 231100b6737d75853ff5bfc2645cfa96115f38d1 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.helpers;
import android.util.Log;
import com.android.os.AtomsProto.Atom;
import com.android.os.StatsLog.GaugeBucketInfo;
import com.android.os.StatsLog.GaugeMetricData;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
* CpuUsageHelper consist of helper methods to set the app
* cpu usage configs in statsd to track the cpu usage related
* metrics and retrieve the necessary information from statsd
* using the config id.
*/
public class CpuUsageHelper implements ICollectorHelper<Long> {
private static final String LOG_TAG = CpuUsageHelper.class.getSimpleName();
private static final String CPU_USAGE_PKG_UID = "cpu_usage_pkg_or_uid";
private static final String CPU_USAGE_FREQ = "cpu_usage_freq";
private static final String TOTAL_CPU_USAGE = "total_cpu_usage";
private static final String TOTAL_CPU_USAGE_FREQ = "total_cpu_usage_freq";
private static final String CLUSTER_ID = "cluster";
private static final String FREQ_INDEX = "freq_index";
private static final String USER_TIME = "user_time";
private static final String SYSTEM_TIME = "system_time";
private static final String TOTAL_CPU_TIME = "total_cpu_time";
private static final String CPU_UTILIZATION = "cpu_utilization_average_per_core_percent";
private StatsdHelper mStatsdHelper = new StatsdHelper();
private boolean isPerFreqDisabled;
private boolean isPerPkgDisabled;
private boolean isTotalPkgDisabled;
private boolean isTotalFreqDisabled;
private boolean isCpuUtilizationEnabled;
private long mStartTime;
private long mEndTime;
private Integer mCpuCores = null;
@Override
public boolean startCollecting() {
Log.i(LOG_TAG, "Adding CpuUsage config to statsd.");
List<Integer> atomIdList = new ArrayList<>();
// Add the atoms to be tracked.
atomIdList.add(Atom.CPU_TIME_PER_UID_FIELD_NUMBER);
atomIdList.add(Atom.CPU_TIME_PER_FREQ_FIELD_NUMBER);
if (isCpuUtilizationEnabled) {
mStartTime = System.currentTimeMillis();
}
return mStatsdHelper.addGaugeConfig(atomIdList);
}
@Override
public Map<String, Long> getMetrics() {
Map<String, Long> cpuUsageFinalMap = new HashMap<>();
List<GaugeMetricData> gaugeMetricList = mStatsdHelper.getGaugeMetrics();
if (isCpuUtilizationEnabled) {
mEndTime = System.currentTimeMillis();
}
ListMultimap<String, Long> cpuUsageMap = ArrayListMultimap.create();
for (GaugeMetricData gaugeMetric : gaugeMetricList) {
Log.v(LOG_TAG, "Bucket Size: " + gaugeMetric.getBucketInfoCount());
for (GaugeBucketInfo gaugeBucketInfo : gaugeMetric.getBucketInfoList()) {
for (Atom atom : gaugeBucketInfo.getAtomList()) {
// Track CPU usage in user time and system time per package or UID
if (atom.getCpuTimePerUid().hasUid()) {
int uId = atom.getCpuTimePerUid().getUid();
String packageName = mStatsdHelper.getPackageName(uId);
// Convert to milliseconds to compare with CpuTimePerFreq
long userTimeMillis = atom.getCpuTimePerUid().getUserTimeMicros() / 1000;
long sysTimeMillis = atom.getCpuTimePerUid().getSysTimeMicros() / 1000;
Log.v(LOG_TAG, String.format("Uid:%d, Pkg Name: %s, User_Time: %d,"
+ " System_Time: %d", uId, packageName, userTimeMillis,
sysTimeMillis));
// Use the package name if exist for the UID otherwise use the UID.
// Note: UID for the apps will be different across the builds.
// It is possible to have multiple bucket info. Track all the gauge info
// and take the difference of the first and last to compute the
// final usage.
String finalUserTimeKey = MetricUtility.constructKey(CPU_USAGE_PKG_UID,
(packageName == null) ? String.valueOf(uId) : packageName,
USER_TIME);
String finalSystemTimeKey = MetricUtility.constructKey(CPU_USAGE_PKG_UID,
(packageName == null) ? String.valueOf(uId) : packageName,
SYSTEM_TIME);
cpuUsageMap.put(finalUserTimeKey, userTimeMillis);
cpuUsageMap.put(finalSystemTimeKey, sysTimeMillis);
}
// Track cpu usage per cluster_id and freq_index
if (atom.getCpuTimePerFreq().hasFreqIndex()) {
int clusterId = atom.getCpuTimePerFreq().getCluster();
int freqIndex = atom.getCpuTimePerFreq().getFreqIndex();
long timeInFreq = atom.getCpuTimePerFreq().getTimeMillis();
Log.v(LOG_TAG, String.format("Cluster Id: %d FreqIndex: %d,"
+ " Time_in_Freq: %d", clusterId, freqIndex, timeInFreq));
String finalFreqIndexKey = MetricUtility.constructKey(
CPU_USAGE_FREQ, CLUSTER_ID, String.valueOf(clusterId), FREQ_INDEX,
String.valueOf(freqIndex));
cpuUsageMap.put(finalFreqIndexKey, timeInFreq);
}
}
}
}
// Compute the final result map
Long totalCpuUsage = 0L;
Long totalCpuFreq = 0L;
for (String key : cpuUsageMap.keySet()) {
List<Long> cpuUsageList = cpuUsageMap.get(key);
if (cpuUsageList.size() > 1) {
// Compute the total usage by taking the difference of last and first value.
Long cpuUsage = cpuUsageList.get(cpuUsageList.size() - 1)
- cpuUsageList.get(0);
// Add the final result only if the cpu usage is greater than 0.
if (cpuUsage > 0) {
if (key.startsWith(CPU_USAGE_PKG_UID)
&& !isPerPkgDisabled) {
cpuUsageFinalMap.put(key, cpuUsage);
}
if (key.startsWith(CPU_USAGE_FREQ)
&& !isPerFreqDisabled) {
cpuUsageFinalMap.put(key, cpuUsage);
}
}
// Add the CPU time to their respective (usage or frequency) total metric.
if (key.startsWith(CPU_USAGE_PKG_UID)
&& !isTotalPkgDisabled) {
totalCpuUsage += cpuUsage;
} else if (key.startsWith(CPU_USAGE_FREQ)
&& !isTotalFreqDisabled) {
totalCpuFreq += cpuUsage;
}
}
}
// Put the total results into the final result map.
if (!isTotalPkgDisabled) {
cpuUsageFinalMap.put(TOTAL_CPU_USAGE, totalCpuUsage);
}
if (!isTotalFreqDisabled) {
cpuUsageFinalMap.put(TOTAL_CPU_USAGE_FREQ, totalCpuFreq);
}
// Calculate cpu utilization
if (isCpuUtilizationEnabled) {
long totalCpuTime = (mEndTime - mStartTime) * getCores();
cpuUsageFinalMap.put(TOTAL_CPU_TIME, totalCpuTime);
if (!isTotalPkgDisabled) {
double utilization =
((double) cpuUsageFinalMap.get(TOTAL_CPU_USAGE) / totalCpuTime) * 100;
cpuUsageFinalMap.put(CPU_UTILIZATION, (long) utilization);
}
}
return cpuUsageFinalMap;
}
/**
* Remove the statsd config used to track the cpu usage metrics.
*/
@Override
public boolean stopCollecting() {
return mStatsdHelper.removeStatsConfig();
}
/**
* Disable the cpu metric collection per package.
*/
public void setDisablePerPackage() {
isPerPkgDisabled = true;
}
/**
* Disable the cpu metric collection per frequency.
*/
public void setDisablePerFrequency() {
isPerFreqDisabled = true;
}
/**
* Disable the total cpu metric collection by all the packages.
*/
public void setDisableTotalPackage() {
isTotalPkgDisabled = true;
}
/**
* Disable the total cpu metric collection by all the frequency.
*/
public void setDisableTotalFrequency() {
isTotalFreqDisabled = true;
}
/**
* Enable the collection of cpu utilization.
*/
public void setEnableCpuUtilization() {
isCpuUtilizationEnabled = true;
}
/**
* return the number of cores that the device has.
*/
private int getCores() {
if (mCpuCores == null) {
int count = 0;
// every core corresponds to a folder named "cpu[0-9]+" under /sys/devices/system/cpu
File cpuDir = new File("/sys/devices/system/cpu");
File[] files = cpuDir.listFiles();
for (File file : files) {
if (Pattern.matches("cpu[0-9]+", file.getName())) {
++count;
}
}
mCpuCores = count;
}
return mCpuCores.intValue();
}
}