From e7f1410a90a00c97467408f6486d64aa58bf13d0 Mon Sep 17 00:00:00 2001 From: Rafal Slawik Date: Tue, 16 Feb 2021 18:54:11 +0000 Subject: [PATCH] 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 --- .../internal/os/KernelCpuBpfTracking.java | 70 ++++++++++- .../os/KernelCpuTotalBpfMapReader.java | 26 ++-- .../internal/os/KernelCpuUidBpfMapReader.java | 11 +- ...droid_internal_os_KernelCpuBpfTracking.cpp | 40 ++++++ ...internal_os_KernelCpuTotalBpfMapReader.cpp | 32 ++--- ...d_internal_os_KernelCpuUidBpfMapReader.cpp | 26 +--- .../stats/pull/StatsPullAtomService.java | 118 +++++++----------- 7 files changed, 189 insertions(+), 134 deletions(-) diff --git a/core/java/com/android/internal/os/KernelCpuBpfTracking.java b/core/java/com/android/internal/os/KernelCpuBpfTracking.java index 28525478be05e..387d3270f5e44 100644 --- a/core/java/com/android/internal/os/KernelCpuBpfTracking.java +++ b/core/java/com/android/internal/os/KernelCpuBpfTracking.java @@ -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; + } } diff --git a/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java b/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java index 06760e140de1b..553eda48e61d9 100644 --- a/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java +++ b/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java @@ -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(); } diff --git a/core/java/com/android/internal/os/KernelCpuUidBpfMapReader.java b/core/java/com/android/internal/os/KernelCpuUidBpfMapReader.java index dafb924f37bd1..52c0c3fa93df2 100644 --- a/core/java/com/android/internal/os/KernelCpuUidBpfMapReader.java +++ b/core/java/com/android/internal/os/KernelCpuUidBpfMapReader.java @@ -68,14 +68,15 @@ public abstract class KernelCpuUidBpfMapReader { final String mTag = this.getClass().getSimpleName(); private int mErrors = 0; - private boolean mTracking = false; protected SparseArray 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) { diff --git a/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp b/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp index e6a82f6d0cb58..6b41b2ec8f939 100644 --- a/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp +++ b/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp @@ -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 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(allFreqs.data())); + } + return ar; +} + +static jintArray KernelCpuBpfTracking_getFreqsClustersInternal(JNIEnv *env, jobject) { + auto freqs = android::bpf::getCpuFreqs(); + if (!freqs) return NULL; + + std::vector 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(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) { diff --git a/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp b/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp index 72492381e31ac..ad43014d321fd 100644 --- a/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp +++ b/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp @@ -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 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(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) { diff --git a/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp b/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp index 7c68de5044171..7900d301dbb09 100644 --- a/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp +++ b/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp @@ -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 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(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); diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 342dbfa3f59d2..6aa7c8a290c1d 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -1471,12 +1471,18 @@ public class StatsPullAtomService extends SystemService { } int pullCpuTimePerClusterFreqLocked(int atomTag, List 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 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 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 pulledData, int threadGroup, long[] cpuTimesUs, - int clusters, long[] freqs, int[] freqClusters) { + int atomTag, List 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(