Manage time_in_state tracking from one place

Move functionality related to CPU time_in_state tracking to the
KernelCpuBpfTracking class:
- when to attach the eBPF program to start tracking
- provide available CPU frequencies
- provide CPU cluster (policy) information
- unify the interface of KernelCpuTotalBpfMapReader with other readers

This removes the dependency on the power profile when reading the
information about available frequencies and clusters. It also reduces
the code duplication for transforming that information in
StatsPullAtomService.

Bug: 179485697
Test: existing tests pass
Test: atest CpuStatsTests
Test: cmd stats pull-source 10010
Test: cmd stats pull-source 10095
Test: cmd stats pull-source 10096
Test: cmd stats pull-source 10098
Test: atest FrameworksCoreTests
Change-Id: I17041014343da167b8bdf027ad83887e78ba5c2d
This commit is contained in:
Rafal Slawik
2021-02-16 18:54:11 +00:00
parent 2441a6bb3a
commit e7f1410a90
7 changed files with 189 additions and 134 deletions

View File

@@ -16,11 +16,79 @@
package com.android.internal.os;
/** CPU tracking using eBPF. */
import android.annotation.Nullable;
/**
* CPU tracking using eBPF.
*
* The tracking state and data about available frequencies are cached to avoid JNI calls and
* creating temporary arrays. The data is stored in a format that is convenient for metrics
* computation.
*
* Synchronization is not needed because the underlying native library can be invoked concurrently
* and getters are idempotent.
*/
public final class KernelCpuBpfTracking {
private static boolean sTracking = false;
/** Cached mapping from frequency index to frequency in kHz. */
private static long[] sFreqs = null;
/** Cached mapping from frequency index to CPU cluster / policy. */
private static int[] sFreqsClusters = null;
private KernelCpuBpfTracking() {
}
/** Returns whether CPU tracking using eBPF is supported. */
public static native boolean isSupported();
/** Starts CPU tracking using eBPF. */
public static boolean startTracking() {
if (!sTracking) {
sTracking = startTrackingInternal();
}
return sTracking;
}
private static native boolean startTrackingInternal();
/** Returns frequencies in kHz on which CPU is tracked. Empty if not supported. */
public static long[] getFreqs() {
if (sFreqs == null) {
long[] freqs = getFreqsInternal();
if (freqs == null) {
return new long[0];
}
sFreqs = freqs;
}
return sFreqs;
}
@Nullable
static native long[] getFreqsInternal();
/**
* Returns the cluster (policy) number for each frequency on which CPU is tracked. Empty if
* not supported.
*/
public static int[] getFreqsClusters() {
if (sFreqsClusters == null) {
int[] freqsClusters = getFreqsClustersInternal();
if (freqsClusters == null) {
return new int[0];
}
sFreqsClusters = freqsClusters;
}
return sFreqsClusters;
}
@Nullable
private static native int[] getFreqsClustersInternal();
/** Returns the number of clusters (policies). */
public static int getClusters() {
int[] freqClusters = getFreqsClusters();
return freqClusters.length > 0 ? freqClusters[freqClusters.length - 1] + 1 : 0;
}
}

View File

@@ -16,22 +16,22 @@
package com.android.internal.os;
/**
* Reads total CPU time bpf map.
*/
import android.annotation.Nullable;
/** Reads total CPU time bpf map. */
public final class KernelCpuTotalBpfMapReader {
private KernelCpuTotalBpfMapReader() {
}
/** Reads total CPU time from bpf map. */
public static native boolean read(Callback callback);
/** Callback accepting values read from bpf map. */
public interface Callback {
/**
* Accepts values read from bpf map: cluster index, frequency in kilohertz and time in
* milliseconds that the cpu cluster spent at the frequency (excluding sleep).
*/
void accept(int cluster, int freqKhz, long timeMs);
/** Reads total CPU times (excluding sleep) per frequency in milliseconds from bpf map. */
@Nullable
public static long[] read() {
if (!KernelCpuBpfTracking.startTracking()) {
return null;
}
return readInternal();
}
@Nullable
private static native long[] readInternal();
}

View File

@@ -68,14 +68,15 @@ public abstract class KernelCpuUidBpfMapReader {
final String mTag = this.getClass().getSimpleName();
private int mErrors = 0;
private boolean mTracking = false;
protected SparseArray<long[]> mData = new SparseArray<>();
private long mLastReadTime = 0;
protected final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
protected final ReentrantReadWriteLock.ReadLock mReadLock = mLock.readLock();
protected final ReentrantReadWriteLock.WriteLock mWriteLock = mLock.writeLock();
public native boolean startTrackingBpfTimes();
public boolean startTrackingBpfTimes() {
return KernelCpuBpfTracking.startTracking();
}
protected abstract boolean readBpfData();
@@ -116,7 +117,7 @@ public abstract class KernelCpuUidBpfMapReader {
if (mErrors > ERROR_THRESHOLD) {
return null;
}
if (!mTracking && !startTrackingBpfTimes()) {
if (!startTrackingBpfTimes()) {
Slog.w(mTag, "Failed to start tracking");
mErrors++;
return null;
@@ -182,7 +183,9 @@ public abstract class KernelCpuUidBpfMapReader {
protected final native boolean readBpfData();
@Override
public final native long[] getDataDimensions();
public final long[] getDataDimensions() {
return KernelCpuBpfTracking.getFreqsInternal();
}
@Override
public void removeUidsInRange(int startUid, int endUid) {

View File

@@ -24,8 +24,48 @@ static jboolean KernelCpuBpfTracking_isSupported(JNIEnv *, jobject) {
return android::bpf::isTrackingUidTimesSupported() ? JNI_TRUE : JNI_FALSE;
}
static jboolean KernelCpuBpfTracking_startTrackingInternal(JNIEnv *, jobject) {
return android::bpf::startTrackingUidTimes();
}
static jlongArray KernelCpuBpfTracking_getFreqsInternal(JNIEnv *env, jobject) {
auto freqs = android::bpf::getCpuFreqs();
if (!freqs) return NULL;
std::vector<uint64_t> allFreqs;
for (const auto &vec : *freqs) std::copy(vec.begin(), vec.end(), std::back_inserter(allFreqs));
auto ar = env->NewLongArray(allFreqs.size());
if (ar != NULL) {
env->SetLongArrayRegion(ar, 0, allFreqs.size(),
reinterpret_cast<const jlong *>(allFreqs.data()));
}
return ar;
}
static jintArray KernelCpuBpfTracking_getFreqsClustersInternal(JNIEnv *env, jobject) {
auto freqs = android::bpf::getCpuFreqs();
if (!freqs) return NULL;
std::vector<uint32_t> freqsClusters;
uint32_t clusters = freqs->size();
for (uint32_t c = 0; c < clusters; ++c) {
freqsClusters.insert(freqsClusters.end(), (*freqs)[c].size(), c);
}
auto ar = env->NewIntArray(freqsClusters.size());
if (ar != NULL) {
env->SetIntArrayRegion(ar, 0, freqsClusters.size(),
reinterpret_cast<const jint *>(freqsClusters.data()));
}
return ar;
}
static const JNINativeMethod methods[] = {
{"isSupported", "()Z", (void *)KernelCpuBpfTracking_isSupported},
{"startTrackingInternal", "()Z", (void *)KernelCpuBpfTracking_startTrackingInternal},
{"getFreqsInternal", "()[J", (void *)KernelCpuBpfTracking_getFreqsInternal},
{"getFreqsClustersInternal", "()[I", (void *)KernelCpuBpfTracking_getFreqsClustersInternal},
};
int register_com_android_internal_os_KernelCpuBpfTracking(JNIEnv *env) {

View File

@@ -20,33 +20,27 @@
namespace android {
static jboolean KernelCpuTotalBpfMapReader_read(JNIEnv *env, jobject, jobject callback) {
jclass callbackClass = env->GetObjectClass(callback);
jmethodID callbackMethod = env->GetMethodID(callbackClass, "accept", "(IIJ)V");
if (callbackMethod == 0) {
return JNI_FALSE;
}
auto freqs = android::bpf::getCpuFreqs();
if (!freqs) return JNI_FALSE;
static jlongArray KernelCpuTotalBpfMapReader_readInternal(JNIEnv *env, jobject) {
auto freqTimes = android::bpf::getTotalCpuFreqTimes();
if (!freqTimes) return JNI_FALSE;
auto freqsClusterSize = (*freqs).size();
for (uint32_t clusterIndex = 0; clusterIndex < freqsClusterSize; ++clusterIndex) {
auto freqsSize = (*freqs)[clusterIndex].size();
for (uint32_t freqIndex = 0; freqIndex < freqsSize; ++freqIndex) {
env->CallVoidMethod(callback, callbackMethod, clusterIndex,
(*freqs)[clusterIndex][freqIndex],
(*freqTimes)[clusterIndex][freqIndex] / 1000000);
std::vector<uint64_t> allTimes;
for (const auto &vec : *freqTimes) {
for (const auto &timeNs : vec) {
allTimes.push_back(timeNs / 1000000);
}
}
return JNI_TRUE;
auto ar = env->NewLongArray(allTimes.size());
if (ar != NULL) {
env->SetLongArrayRegion(ar, 0, allTimes.size(),
reinterpret_cast<const jlong *>(allTimes.data()));
}
return ar;
}
static const JNINativeMethod methods[] = {
{"read", "(Lcom/android/internal/os/KernelCpuTotalBpfMapReader$Callback;)Z",
(void *)KernelCpuTotalBpfMapReader_read},
{"readInternal", "()[J", (void *)KernelCpuTotalBpfMapReader_readInternal},
};
int register_com_android_internal_os_KernelCpuTotalBpfMapReader(JNIEnv *env) {

View File

@@ -82,25 +82,9 @@ static jboolean KernelCpuUidFreqTimeBpfMapReader_readBpfData(JNIEnv *env, jobjec
return true;
}
static jlongArray KernelCpuUidFreqTimeBpfMapReader_getDataDimensions(JNIEnv *env, jobject) {
auto freqs = android::bpf::getCpuFreqs();
if (!freqs) return NULL;
std::vector<uint64_t> allFreqs;
for (const auto &vec : *freqs) std::copy(vec.begin(), vec.end(), std::back_inserter(allFreqs));
auto ar = env->NewLongArray(allFreqs.size());
if (ar != NULL) {
env->SetLongArrayRegion(ar, 0, allFreqs.size(),
reinterpret_cast<const jlong *>(allFreqs.data()));
}
return ar;
}
static const JNINativeMethod gFreqTimeMethods[] = {
{"removeUidRange", "(II)Z", (void *)KernelCpuUidFreqTimeBpfMapReader_removeUidRange},
{"readBpfData", "()Z", (void *)KernelCpuUidFreqTimeBpfMapReader_readBpfData},
{"getDataDimensions", "()[J", (void *)KernelCpuUidFreqTimeBpfMapReader_getDataDimensions},
};
static jboolean KernelCpuUidActiveTimeBpfMapReader_readBpfData(JNIEnv *env, jobject thiz) {
@@ -186,10 +170,6 @@ static const readerMethods gAllMethods[] = {
{"KernelCpuUidClusterTimeBpfMapReader", gClusterTimeMethods, NELEM(gClusterTimeMethods)},
};
static jboolean KernelCpuUidBpfMapReader_startTrackingBpfTimes(JNIEnv *, jobject) {
return android::bpf::startTrackingUidTimes();
}
int register_com_android_internal_os_KernelCpuUidBpfMapReader(JNIEnv *env) {
gSparseArrayClassInfo.clazz = FindClassOrDie(env, "android/util/SparseArray");
gSparseArrayClassInfo.clazz = MakeGlobalRefOrDie(env, gSparseArrayClassInfo.clazz);
@@ -198,14 +178,10 @@ int register_com_android_internal_os_KernelCpuUidBpfMapReader(JNIEnv *env) {
gSparseArrayClassInfo.get =
GetMethodIDOrDie(env, gSparseArrayClassInfo.clazz, "get", "(I)Ljava/lang/Object;");
constexpr auto readerName = "com/android/internal/os/KernelCpuUidBpfMapReader";
constexpr JNINativeMethod method = {"startTrackingBpfTimes", "()Z",
(void *)KernelCpuUidBpfMapReader_startTrackingBpfTimes};
int ret = RegisterMethodsOrDie(env, readerName, &method, 1);
if (ret < 0) return ret;
auto c = FindClassOrDie(env, readerName);
gmData = GetFieldIDOrDie(env, c, "mData", "Landroid/util/SparseArray;");
int ret = 0;
for (const auto &m : gAllMethods) {
auto fullName = android::base::StringPrintf("%s$%s", readerName, m.name);
ret = RegisterMethodsOrDie(env, fullName.c_str(), m.methods, m.numMethods);

View File

@@ -1471,12 +1471,18 @@ public class StatsPullAtomService extends SystemService {
}
int pullCpuTimePerClusterFreqLocked(int atomTag, List<StatsEvent> pulledData) {
boolean success = KernelCpuTotalBpfMapReader.read((cluster, freq, timeMs) -> {
pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, cluster, freq, timeMs));
});
if (!success) {
int[] freqsClusters = KernelCpuBpfTracking.getFreqsClusters();
long[] freqs = KernelCpuBpfTracking.getFreqs();
long[] timesMs = KernelCpuTotalBpfMapReader.read();
if (timesMs == null) {
return StatsManager.PULL_SKIP;
}
for (int freqIndex = 0; freqIndex < timesMs.length; ++freqIndex) {
int cluster = freqsClusters[freqIndex];
long freq = freqs[freqIndex];
long timeMs = timesMs[freqIndex];
pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, cluster, freq, timeMs));
}
return StatsManager.PULL_SUCCESS;
}
@@ -1503,48 +1509,42 @@ public class StatsPullAtomService extends SystemService {
}
private void registerCpuCyclesPerUidCluster() {
int tagId = FrameworkStatsLog.CPU_CYCLES_PER_UID_CLUSTER;
PullAtomMetadata metadata = new PullAtomMetadata.Builder()
.setAdditiveFields(new int[] {3, 4, 5})
.build();
mStatsManager.setPullAtomCallback(
tagId,
metadata,
DIRECT_EXECUTOR,
mStatsCallbackImpl
);
// If eBPF tracking is not support, the procfs fallback is used if the kernel knows about
// CPU frequencies.
if (KernelCpuBpfTracking.isSupported() || KernelCpuBpfTracking.getClusters() > 0) {
int tagId = FrameworkStatsLog.CPU_CYCLES_PER_UID_CLUSTER;
PullAtomMetadata metadata = new PullAtomMetadata.Builder()
.setAdditiveFields(new int[] {3, 4, 5})
.build();
mStatsManager.setPullAtomCallback(
tagId,
metadata,
DIRECT_EXECUTOR,
mStatsCallbackImpl
);
}
}
int pullCpuCyclesPerUidClusterLocked(int atomTag, List<StatsEvent> pulledData) {
// TODO(b/179485697): Remove power profile dependency.
PowerProfile powerProfile = new PowerProfile(mContext);
// Frequency index to frequency mapping.
long[] freqs = mCpuUidFreqTimeReader.readFreqs(powerProfile);
// Frequency index to cluster mapping.
int[] freqClusters = new int[freqs.length];
// Frequency index to power mapping.
double[] freqPowers = new double[freqs.length];
// Number of clusters.
int clusters;
// Initialize frequency mappings.
int[] freqsClusters = KernelCpuBpfTracking.getFreqsClusters();
int clusters = KernelCpuBpfTracking.getClusters();
long[] freqs = KernelCpuBpfTracking.getFreqs();
double[] freqsPowers = new double[freqs.length];
// Initialize frequency power mapping.
{
int cluster = 0;
int freqClusterIndex = 0;
long lastFreq = -1;
int lastCluster = -1;
for (int freqIndex = 0; freqIndex < freqs.length; ++freqIndex, ++freqClusterIndex) {
long currFreq = freqs[freqIndex];
if (currFreq <= lastFreq) {
cluster++;
int cluster = freqsClusters[freqIndex];
if (cluster != lastCluster) {
freqClusterIndex = 0;
}
freqClusters[freqIndex] = cluster;
freqPowers[freqIndex] =
powerProfile.getAveragePowerForCpuCore(cluster, freqClusterIndex);
lastFreq = currFreq;
}
lastCluster = cluster;
clusters = cluster + 1;
freqsPowers[freqIndex] =
powerProfile.getAveragePowerForCpuCore(cluster, freqClusterIndex);
}
}
// Aggregate 0: mcycles, 1: runtime ms, 2: power profile estimate for the same uids for
@@ -1570,12 +1570,12 @@ public class StatsPullAtomService extends SystemService {
}
for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
int cluster = freqClusters[freqIndex];
int cluster = freqsClusters[freqIndex];
long timeMs = cpuFreqTimeMs[freqIndex];
values[cluster * CPU_CYCLES_PER_UID_CLUSTER_VALUES] += freqs[freqIndex] * timeMs;
values[cluster * CPU_CYCLES_PER_UID_CLUSTER_VALUES + 1] += timeMs;
values[cluster * CPU_CYCLES_PER_UID_CLUSTER_VALUES + 2] +=
freqPowers[freqIndex] * timeMs;
freqsPowers[freqIndex] * timeMs;
}
});
@@ -1665,34 +1665,6 @@ public class StatsPullAtomService extends SystemService {
}
int pullCpuCyclesPerThreadGroupCluster(int atomTag, List<StatsEvent> pulledData) {
// TODO(b/179485697): Remove power profile dependency.
PowerProfile powerProfile = new PowerProfile(mContext);
// Frequency index to frequency mapping.
long[] freqs = mCpuUidFreqTimeReader.readFreqs(powerProfile);
if (freqs == null) {
return StatsManager.PULL_SKIP;
}
// Frequency index to cluster mapping.
int[] freqClusters = new int[freqs.length];
// Number of clusters.
int clusters;
// Initialize frequency mappings.
{
int cluster = 0;
long lastFreq = -1;
for (int freqIndex = 0; freqIndex < freqs.length; ++freqIndex) {
long currFreq = freqs[freqIndex];
if (currFreq <= lastFreq) {
cluster++;
}
freqClusters[freqIndex] = cluster;
lastFreq = currFreq;
}
clusters = cluster + 1;
}
SystemServiceCpuThreadTimes times = LocalServices.getService(BatteryStatsInternal.class)
.getSystemServiceCpuThreadTimes();
if (times == null) {
@@ -1701,22 +1673,24 @@ public class StatsPullAtomService extends SystemService {
addCpuCyclesPerThreadGroupClusterAtoms(atomTag, pulledData,
FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER__THREAD_GROUP__SYSTEM_SERVER,
times.threadCpuTimesUs, clusters, freqs, freqClusters);
times.threadCpuTimesUs);
addCpuCyclesPerThreadGroupClusterAtoms(atomTag, pulledData,
FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER__THREAD_GROUP__SYSTEM_SERVER_BINDER,
times.binderThreadCpuTimesUs, clusters, freqs, freqClusters);
times.binderThreadCpuTimesUs);
return StatsManager.PULL_SUCCESS;
}
private static void addCpuCyclesPerThreadGroupClusterAtoms(
int atomTag, List<StatsEvent> pulledData, int threadGroup, long[] cpuTimesUs,
int clusters, long[] freqs, int[] freqClusters) {
int atomTag, List<StatsEvent> pulledData, int threadGroup, long[] cpuTimesUs) {
int[] freqsClusters = KernelCpuBpfTracking.getFreqsClusters();
int clusters = KernelCpuBpfTracking.getClusters();
long[] freqs = KernelCpuBpfTracking.getFreqs();
long[] aggregatedCycles = new long[clusters];
long[] aggregatedTimesUs = new long[clusters];
for (int i = 0; i < cpuTimesUs.length; ++i) {
aggregatedCycles[freqClusters[i]] += freqs[i] * cpuTimesUs[i] / 1_000;
aggregatedTimesUs[freqClusters[i]] += cpuTimesUs[i];
aggregatedCycles[freqsClusters[i]] += freqs[i] * cpuTimesUs[i] / 1_000;
aggregatedTimesUs[freqsClusters[i]] += cpuTimesUs[i];
}
for (int cluster = 0; cluster < clusters; ++cluster) {
pulledData.add(FrameworkStatsLog.buildStatsEvent(