summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/com/android/internal/os/KernelCpuUidBpfMapReader.java202
-rw-r--r--core/java/com/android/internal/os/KernelCpuUidTimeReader.java355
-rw-r--r--core/jni/Android.bp2
-rw-r--r--core/jni/AndroidRuntime.cpp2
-rw-r--r--core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp217
-rw-r--r--core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java1
-rw-r--r--core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java107
-rw-r--r--core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java237
-rw-r--r--core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java109
-rw-r--r--core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java130
10 files changed, 1207 insertions, 155 deletions
diff --git a/core/java/com/android/internal/os/KernelCpuUidBpfMapReader.java b/core/java/com/android/internal/os/KernelCpuUidBpfMapReader.java
new file mode 100644
index 000000000000..26f81d9db9c8
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelCpuUidBpfMapReader.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2020 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.internal.os;
+
+import android.os.StrictMode;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Reads cpu time bpf maps.
+ *
+ * It is implemented as singletons for each separate set of per-UID times. Get___Instance() method
+ * returns the corresponding reader instance. In order to prevent frequent GC, it reuses the same
+ * SparseArray to store data read from BPF maps.
+ *
+ * A KernelCpuUidBpfMapReader instance keeps an error counter. When the number of read errors within
+ * that instance accumulates to 5, this instance will reject all further read requests.
+ *
+ * Data fetched within last 500ms is considered fresh, since the reading lifecycle can take up to
+ * 25ms. KernelCpuUidBpfMapReader always tries to use cache if it is fresh and valid, but it can
+ * be disabled through a parameter.
+ *
+ * A KernelCpuUidBpfMapReader instance is thread-safe. It acquires a write lock when reading the bpf
+ * map, releases it right after, then acquires a read lock before returning a BpfMapIterator. Caller
+ * is responsible for closing BpfMapIterator (also auto-closable) after reading, otherwise deadlock
+ * will occur.
+ */
+public abstract class KernelCpuUidBpfMapReader {
+ private static final int ERROR_THRESHOLD = 5;
+ private static final long FRESHNESS_MS = 500L;
+
+ private static final KernelCpuUidBpfMapReader FREQ_TIME_READER =
+ new KernelCpuUidFreqTimeBpfMapReader();
+
+ private static final KernelCpuUidBpfMapReader ACTIVE_TIME_READER =
+ new KernelCpuUidActiveTimeBpfMapReader();
+
+ private static final KernelCpuUidBpfMapReader CLUSTER_TIME_READER =
+ new KernelCpuUidClusterTimeBpfMapReader();
+
+ static KernelCpuUidBpfMapReader getFreqTimeReaderInstance() {
+ return FREQ_TIME_READER;
+ }
+
+ static KernelCpuUidBpfMapReader getActiveTimeReaderInstance() {
+ return ACTIVE_TIME_READER;
+ }
+
+ static KernelCpuUidBpfMapReader getClusterTimeReaderInstance() {
+ return CLUSTER_TIME_READER;
+ }
+
+ 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();
+
+ protected abstract boolean readBpfData();
+
+ /**
+ * Returns an array of metadata used to inform the caller of 1) the size of array required by
+ * getNextUid and 2) how to interpret the raw data copied to that array.
+ */
+ public abstract long[] getDataDimensions();
+
+ public void removeUidsInRange(int startUid, int endUid) {
+ if (mErrors > ERROR_THRESHOLD) {
+ return;
+ }
+ mWriteLock.lock();
+ int firstIndex = mData.indexOfKey(startUid);
+ int lastIndex = mData.indexOfKey(endUid);
+ mData.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
+ mWriteLock.unlock();
+ }
+
+ public BpfMapIterator open() {
+ return open(false);
+ }
+
+ public BpfMapIterator open(boolean ignoreCache) {
+ if (mErrors > ERROR_THRESHOLD) {
+ return null;
+ }
+ if (!mTracking && !startTrackingBpfTimes()) {
+ Slog.w(mTag, "Failed to start tracking");
+ mErrors++;
+ return null;
+ }
+ if (ignoreCache) {
+ mWriteLock.lock();
+ } else {
+ mReadLock.lock();
+ if (dataValid()) {
+ return new BpfMapIterator();
+ }
+ mReadLock.unlock();
+ mWriteLock.lock();
+ if (dataValid()) {
+ mReadLock.lock();
+ mWriteLock.unlock();
+ return new BpfMapIterator();
+ }
+ }
+ if (readBpfData()) {
+ mLastReadTime = SystemClock.elapsedRealtime();
+ mReadLock.lock();
+ mWriteLock.unlock();
+ return new BpfMapIterator();
+ }
+
+ mWriteLock.unlock();
+ mErrors++;
+ Slog.w(mTag, "Failed to read bpf times");
+ return null;
+ }
+
+ private boolean dataValid() {
+ return mData.size() > 0 && (SystemClock.elapsedRealtime() - mLastReadTime < FRESHNESS_MS);
+ }
+
+ public class BpfMapIterator implements AutoCloseable {
+ private int mPos;
+
+ public BpfMapIterator() {
+ };
+
+ public boolean getNextUid(long[] buf) {
+ if (mPos >= mData.size()) {
+ return false;
+ }
+ buf[0] = mData.keyAt(mPos);
+ System.arraycopy(mData.valueAt(mPos), 0, buf, 1, mData.valueAt(mPos).length);
+ mPos++;
+ return true;
+ }
+
+ public void close() {
+ mReadLock.unlock();
+ }
+ }
+
+ public static class KernelCpuUidFreqTimeBpfMapReader extends KernelCpuUidBpfMapReader {
+
+ private final native boolean removeUidRange(int startUid, int endUid);
+
+ @Override
+ protected final native boolean readBpfData();
+
+ @Override
+ public final native long[] getDataDimensions();
+
+ @Override
+ public void removeUidsInRange(int startUid, int endUid) {
+ mWriteLock.lock();
+ super.removeUidsInRange(startUid, endUid);
+ removeUidRange(startUid, endUid);
+ mWriteLock.unlock();
+ }
+ }
+
+ public static class KernelCpuUidActiveTimeBpfMapReader extends KernelCpuUidBpfMapReader {
+
+ @Override
+ protected final native boolean readBpfData();
+
+ @Override
+ public final native long[] getDataDimensions();
+ }
+
+ public static class KernelCpuUidClusterTimeBpfMapReader extends KernelCpuUidBpfMapReader {
+
+ @Override
+ protected final native boolean readBpfData();
+
+ @Override
+ public final native long[] getDataDimensions();
+ }
+}
diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
index e6d044f4722b..34e75fe57fc4 100644
--- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
@@ -28,6 +28,7 @@ import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.KernelCpuProcStringReader.ProcFileIterator;
+import com.android.internal.os.KernelCpuUidBpfMapReader.BpfMapIterator;
import java.io.BufferedReader;
import java.io.FileWriter;
@@ -57,6 +58,8 @@ public abstract class KernelCpuUidTimeReader<T> {
final SparseArray<T> mLastTimes = new SparseArray<>();
final KernelCpuProcStringReader mReader;
final boolean mThrottle;
+ protected boolean mBpfTimesAvailable;
+ final KernelCpuUidBpfMapReader mBpfReader;
private long mMinTimeBetweenRead = DEFAULT_MIN_TIME_BETWEEN_READ;
private long mLastReadTimeMs = 0;
@@ -73,9 +76,15 @@ public abstract class KernelCpuUidTimeReader<T> {
void onUidCpuTime(int uid, T time);
}
- KernelCpuUidTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
+ KernelCpuUidTimeReader(KernelCpuProcStringReader reader, @Nullable KernelCpuUidBpfMapReader bpfReader, boolean throttle) {
mReader = reader;
mThrottle = throttle;
+ mBpfReader = bpfReader;
+ mBpfTimesAvailable = (mBpfReader != null);
+ }
+
+ KernelCpuUidTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
+ this(reader, null, throttle);
}
/**
@@ -151,9 +160,13 @@ public abstract class KernelCpuUidTimeReader<T> {
}
mLastTimes.put(startUid, null);
mLastTimes.put(endUid, null);
- final int firstIndex = mLastTimes.indexOfKey(startUid);
- final int lastIndex = mLastTimes.indexOfKey(endUid);
+ int firstIndex = mLastTimes.indexOfKey(startUid);
+ int lastIndex = mLastTimes.indexOfKey(endUid);
mLastTimes.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
+
+ if (mBpfTimesAvailable) {
+ mBpfReader.removeUidsInRange(startUid, endUid);
+ }
}
/**
@@ -323,13 +336,13 @@ public abstract class KernelCpuUidTimeReader<T> {
public KernelCpuUidFreqTimeReader(boolean throttle) {
this(UID_TIMES_PROC_FILE, KernelCpuProcStringReader.getFreqTimeReaderInstance(),
- throttle);
+ KernelCpuUidBpfMapReader.getFreqTimeReaderInstance(), throttle);
}
@VisibleForTesting
public KernelCpuUidFreqTimeReader(String procFile, KernelCpuProcStringReader reader,
- boolean throttle) {
- super(reader, throttle);
+ KernelCpuUidBpfMapReader bpfReader, boolean throttle) {
+ super(reader, bpfReader, throttle);
mProcFilePath = Paths.get(procFile);
}
@@ -370,19 +383,24 @@ public abstract class KernelCpuUidTimeReader<T> {
if (!mAllUidTimesAvailable) {
return null;
}
- final int oldMask = StrictMode.allowThreadDiskReadsMask();
- try (BufferedReader reader = Files.newBufferedReader(mProcFilePath)) {
- if (readFreqs(reader.readLine()) == null) {
+ if (mBpfTimesAvailable) {
+ readFreqsThroughBpf();
+ }
+ if (mCpuFreqs == null) {
+ final int oldMask = StrictMode.allowThreadDiskReadsMask();
+ try (BufferedReader reader = Files.newBufferedReader(mProcFilePath)) {
+ if (readFreqs(reader.readLine()) == null) {
+ return null;
+ }
+ } catch (IOException e) {
+ if (++mErrors >= MAX_ERROR_COUNT) {
+ mAllUidTimesAvailable = false;
+ }
+ Slog.e(mTag, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
return null;
+ } finally {
+ StrictMode.setThreadPolicyMask(oldMask);
}
- } catch (IOException e) {
- if (++mErrors >= MAX_ERROR_COUNT) {
- mAllUidTimesAvailable = false;
- }
- Slog.e(mTag, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
- return null;
- } finally {
- StrictMode.setThreadPolicyMask(oldMask);
}
// Check if the freqs in the proc file correspond to per-cluster freqs.
final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs();
@@ -402,6 +420,21 @@ public abstract class KernelCpuUidTimeReader<T> {
return mCpuFreqs;
}
+ private long[] readFreqsThroughBpf() {
+ if (!mBpfTimesAvailable || mBpfReader == null) {
+ return null;
+ }
+ mCpuFreqs = mBpfReader.getDataDimensions();
+ if (mCpuFreqs == null) {
+ return null;
+ }
+ mFreqCount = mCpuFreqs.length;
+ mCurTimes = new long[mFreqCount];
+ mDeltaTimes = new long[mFreqCount];
+ mBuffer = new long[mFreqCount + 1];
+ return mCpuFreqs;
+ }
+
private long[] readFreqs(String line) {
if (line == null) {
return null;
@@ -422,8 +455,45 @@ public abstract class KernelCpuUidTimeReader<T> {
return mCpuFreqs;
}
+ private void processUidDelta(@Nullable Callback<long[]> cb) {
+ final int uid = (int) mBuffer[0];
+ long[] lastTimes = mLastTimes.get(uid);
+ if (lastTimes == null) {
+ lastTimes = new long[mFreqCount];
+ mLastTimes.put(uid, lastTimes);
+ }
+ copyToCurTimes();
+ boolean notify = false;
+ boolean valid = true;
+ for (int i = 0; i < mFreqCount; i++) {
+ // Unit is 10ms.
+ mDeltaTimes[i] = mCurTimes[i] - lastTimes[i];
+ if (mDeltaTimes[i] < 0) {
+ Slog.e(mTag, "Negative delta from freq time proc: " + mDeltaTimes[i]);
+ valid = false;
+ }
+ notify |= mDeltaTimes[i] > 0;
+ }
+ if (notify && valid) {
+ System.arraycopy(mCurTimes, 0, lastTimes, 0, mFreqCount);
+ if (cb != null) {
+ cb.onUidCpuTime(uid, mDeltaTimes);
+ }
+ }
+ }
+
@Override
void readDeltaImpl(@Nullable Callback<long[]> cb) {
+ if (mBpfTimesAvailable) {
+ try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+ if (checkPrecondition(iter)) {
+ while (iter.getNextUid(mBuffer)) {
+ processUidDelta(cb);
+ }
+ return;
+ }
+ }
+ }
try (ProcFileIterator iter = mReader.open(!mThrottle)) {
if (!checkPrecondition(iter)) {
return;
@@ -434,36 +504,24 @@ public abstract class KernelCpuUidTimeReader<T> {
Slog.wtf(mTag, "Invalid line: " + buf.toString());
continue;
}
- final int uid = (int) mBuffer[0];
- long[] lastTimes = mLastTimes.get(uid);
- if (lastTimes == null) {
- lastTimes = new long[mFreqCount];
- mLastTimes.put(uid, lastTimes);
- }
- copyToCurTimes();
- boolean notify = false;
- boolean valid = true;
- for (int i = 0; i < mFreqCount; i++) {
- // Unit is 10ms.
- mDeltaTimes[i] = mCurTimes[i] - lastTimes[i];
- if (mDeltaTimes[i] < 0) {
- Slog.e(mTag, "Negative delta from freq time proc: " + mDeltaTimes[i]);
- valid = false;
- }
- notify |= mDeltaTimes[i] > 0;
- }
- if (notify && valid) {
- System.arraycopy(mCurTimes, 0, lastTimes, 0, mFreqCount);
- if (cb != null) {
- cb.onUidCpuTime(uid, mDeltaTimes);
- }
- }
+ processUidDelta(cb);
}
}
}
@Override
void readAbsoluteImpl(Callback<long[]> cb) {
+ if (mBpfTimesAvailable) {
+ try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+ if (checkPrecondition(iter)) {
+ while (iter.getNextUid(mBuffer)) {
+ copyToCurTimes();
+ cb.onUidCpuTime((int) mBuffer[0], mCurTimes);
+ }
+ return;
+ }
+ }
+ }
try (ProcFileIterator iter = mReader.open(!mThrottle)) {
if (!checkPrecondition(iter)) {
return;
@@ -481,9 +539,22 @@ public abstract class KernelCpuUidTimeReader<T> {
}
private void copyToCurTimes() {
+ long factor = mBpfTimesAvailable ? 1 : 10;
for (int i = 0; i < mFreqCount; i++) {
- mCurTimes[i] = mBuffer[i + 1] * 10;
+ mCurTimes[i] = mBuffer[i + 1] * factor;
+ }
+ }
+
+ private boolean checkPrecondition(BpfMapIterator iter) {
+ if (iter == null) {
+ mBpfTimesAvailable = false;
+ return false;
+ }
+ if (mCpuFreqs != null) {
+ return true;
}
+ mBpfTimesAvailable = (readFreqsThroughBpf() != null);
+ return mBpfTimesAvailable;
}
private boolean checkPrecondition(ProcFileIterator iter) {
@@ -544,16 +615,43 @@ public abstract class KernelCpuUidTimeReader<T> {
private long[] mBuffer;
public KernelCpuUidActiveTimeReader(boolean throttle) {
- super(KernelCpuProcStringReader.getActiveTimeReaderInstance(), throttle);
+ super(KernelCpuProcStringReader.getActiveTimeReaderInstance(),
+ KernelCpuUidBpfMapReader.getActiveTimeReaderInstance(), throttle);
}
@VisibleForTesting
- public KernelCpuUidActiveTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
- super(reader, throttle);
+ public KernelCpuUidActiveTimeReader(KernelCpuProcStringReader reader, KernelCpuUidBpfMapReader bpfReader, boolean throttle) {
+ super(reader, bpfReader, throttle);
+ }
+
+ private void processUidDelta(@Nullable Callback<Long> cb) {
+ int uid = (int) mBuffer[0];
+ long cpuActiveTime = sumActiveTime(mBuffer, mBpfTimesAvailable ? 1 : 10);
+ if (cpuActiveTime > 0) {
+ long delta = cpuActiveTime - mLastTimes.get(uid, 0L);
+ if (delta > 0) {
+ mLastTimes.put(uid, cpuActiveTime);
+ if (cb != null) {
+ cb.onUidCpuTime(uid, delta);
+ }
+ } else if (delta < 0) {
+ Slog.e(mTag, "Negative delta from active time proc: " + delta);
+ }
+ }
}
@Override
void readDeltaImpl(@Nullable Callback<Long> cb) {
+ if (mBpfTimesAvailable) {
+ try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+ if (checkPrecondition(iter)) {
+ while (iter.getNextUid(mBuffer)) {
+ processUidDelta(cb);
+ }
+ return;
+ }
+ }
+ }
try (ProcFileIterator iter = mReader.open(!mThrottle)) {
if (!checkPrecondition(iter)) {
return;
@@ -564,25 +662,30 @@ public abstract class KernelCpuUidTimeReader<T> {
Slog.wtf(mTag, "Invalid line: " + buf.toString());
continue;
}
- int uid = (int) mBuffer[0];
- long cpuActiveTime = sumActiveTime(mBuffer);
- if (cpuActiveTime > 0) {
- long delta = cpuActiveTime - mLastTimes.get(uid, 0L);
- if (delta > 0) {
- mLastTimes.put(uid, cpuActiveTime);
- if (cb != null) {
- cb.onUidCpuTime(uid, delta);
- }
- } else if (delta < 0) {
- Slog.e(mTag, "Negative delta from active time proc: " + delta);
- }
- }
+ processUidDelta(cb);
}
}
}
+ private void processUidAbsolute(@Nullable Callback<Long> cb) {
+ long cpuActiveTime = sumActiveTime(mBuffer, mBpfTimesAvailable ? 1 : 10);
+ if (cpuActiveTime > 0) {
+ cb.onUidCpuTime((int) mBuffer[0], cpuActiveTime);
+ }
+ }
+
@Override
void readAbsoluteImpl(Callback<Long> cb) {
+ if (mBpfTimesAvailable) {
+ try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+ if (checkPrecondition(iter)) {
+ while (iter.getNextUid(mBuffer)) {
+ processUidAbsolute(cb);
+ }
+ return;
+ }
+ }
+ }
try (ProcFileIterator iter = mReader.open(!mThrottle)) {
if (!checkPrecondition(iter)) {
return;
@@ -593,23 +696,38 @@ public abstract class KernelCpuUidTimeReader<T> {
Slog.wtf(mTag, "Invalid line: " + buf.toString());
continue;
}
- long cpuActiveTime = sumActiveTime(mBuffer);
- if (cpuActiveTime > 0) {
- cb.onUidCpuTime((int) mBuffer[0], cpuActiveTime);
- }
+ processUidAbsolute(cb);
}
}
}
- private static long sumActiveTime(long[] times) {
+ private static long sumActiveTime(long[] times, double factor) {
// UID is stored at times[0].
double sum = 0;
for (int i = 1; i < times.length; i++) {
- sum += (double) times[i] * 10 / i; // Unit is 10ms.
+ sum += (double) times[i] * factor / i; // Unit is 10ms.
}
return (long) sum;
}
+ private boolean checkPrecondition(BpfMapIterator iter) {
+ if (iter == null) {
+ mBpfTimesAvailable = false;
+ return false;
+ }
+ if (mCores > 0) {
+ return true;
+ }
+ long[] cores = mBpfReader.getDataDimensions();
+ if (cores == null || cores.length < 1) {
+ mBpfTimesAvailable = false;
+ return false;
+ }
+ mCores = (int) cores[0];
+ mBuffer = new long[mCores + 1];
+ return true;
+ }
+
private boolean checkPrecondition(ProcFileIterator iter) {
if (iter == null || !iter.hasNextLine()) {
// Error logged in KernelCpuProcStringReader.
@@ -664,16 +782,54 @@ public abstract class KernelCpuUidTimeReader<T> {
private long[] mDeltaTime;
public KernelCpuUidClusterTimeReader(boolean throttle) {
- super(KernelCpuProcStringReader.getClusterTimeReaderInstance(), throttle);
+ super(KernelCpuProcStringReader.getClusterTimeReaderInstance(),
+ KernelCpuUidBpfMapReader.getClusterTimeReaderInstance(), throttle);
}
@VisibleForTesting
- public KernelCpuUidClusterTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
- super(reader, throttle);
+ public KernelCpuUidClusterTimeReader(KernelCpuProcStringReader reader,
+ KernelCpuUidBpfMapReader bpfReader, boolean throttle) {
+ super(reader, bpfReader, throttle);
+ }
+
+ void processUidDelta(@Nullable Callback<long[]> cb) {
+ int uid = (int) mBuffer[0];
+ long[] lastTimes = mLastTimes.get(uid);
+ if (lastTimes == null) {
+ lastTimes = new long[mNumClusters];
+ mLastTimes.put(uid, lastTimes);
+ }
+ sumClusterTime();
+ boolean valid = true;
+ boolean notify = false;
+ for (int i = 0; i < mNumClusters; i++) {
+ mDeltaTime[i] = mCurTime[i] - lastTimes[i];
+ if (mDeltaTime[i] < 0) {
+ Slog.e(mTag, "Negative delta from cluster time proc: " + mDeltaTime[i]);
+ valid = false;
+ }
+ notify |= mDeltaTime[i] > 0;
+ }
+ if (notify && valid) {
+ System.arraycopy(mCurTime, 0, lastTimes, 0, mNumClusters);
+ if (cb != null) {
+ cb.onUidCpuTime(uid, mDeltaTime);
+ }
+ }
}
@Override
void readDeltaImpl(@Nullable Callback<long[]> cb) {
+ if (mBpfTimesAvailable) {
+ try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+ if (checkPrecondition(iter)) {
+ while (iter.getNextUid(mBuffer)) {
+ processUidDelta(cb);
+ }
+ return;
+ }
+ }
+ }
try (ProcFileIterator iter = mReader.open(!mThrottle)) {
if (!checkPrecondition(iter)) {
return;
@@ -684,35 +840,24 @@ public abstract class KernelCpuUidTimeReader<T> {
Slog.wtf(mTag, "Invalid line: " + buf.toString());
continue;
}
- int uid = (int) mBuffer[0];
- long[] lastTimes = mLastTimes.get(uid);
- if (lastTimes == null) {
- lastTimes = new long[mNumClusters];
- mLastTimes.put(uid, lastTimes);
- }
- sumClusterTime();
- boolean valid = true;
- boolean notify = false;
- for (int i = 0; i < mNumClusters; i++) {
- mDeltaTime[i] = mCurTime[i] - lastTimes[i];
- if (mDeltaTime[i] < 0) {
- Slog.e(mTag, "Negative delta from cluster time proc: " + mDeltaTime[i]);
- valid = false;
- }
- notify |= mDeltaTime[i] > 0;
- }
- if (notify && valid) {
- System.arraycopy(mCurTime, 0, lastTimes, 0, mNumClusters);
- if (cb != null) {
- cb.onUidCpuTime(uid, mDeltaTime);
- }
- }
+ processUidDelta(cb);
}
}
}
@Override
void readAbsoluteImpl(Callback<long[]> cb) {
+ if (mBpfTimesAvailable) {
+ try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+ if (checkPrecondition(iter)) {
+ while (iter.getNextUid(mBuffer)) {
+ sumClusterTime();
+ cb.onUidCpuTime((int) mBuffer[0], mCurTime);
+ }
+ return;
+ }
+ }
+ }
try (ProcFileIterator iter = mReader.open(!mThrottle)) {
if (!checkPrecondition(iter)) {
return;
@@ -730,17 +875,45 @@ public abstract class KernelCpuUidTimeReader<T> {
}
private void sumClusterTime() {
+ double factor = mBpfTimesAvailable ? 1 : 10;
// UID is stored at mBuffer[0].
int core = 1;
for (int i = 0; i < mNumClusters; i++) {
double sum = 0;
for (int j = 1; j <= mCoresOnClusters[i]; j++) {
- sum += (double) mBuffer[core++] * 10 / j; // Unit is 10ms.
+ sum += (double) mBuffer[core++] * factor / j; // Unit is 10ms.
}
mCurTime[i] = (long) sum;
}
}
+ private boolean checkPrecondition(BpfMapIterator iter) {
+ if (iter == null) {
+ mBpfTimesAvailable = false;
+ return false;
+ }
+ if (mNumClusters > 0) {
+ return true;
+ }
+ long[] coresOnClusters = mBpfReader.getDataDimensions();
+ if (coresOnClusters == null || coresOnClusters.length < 1) {
+ mBpfTimesAvailable = false;
+ return false;
+ }
+ mNumClusters = coresOnClusters.length;
+ mCoresOnClusters = new int[mNumClusters];
+ int cores = 0;
+ for (int i = 0; i < mNumClusters; i++) {
+ mCoresOnClusters[i] = (int) coresOnClusters[i];
+ cores += mCoresOnClusters[i];
+ }
+ mNumCores = cores;
+ mBuffer = new long[cores + 1];
+ mCurTime = new long[mNumClusters];
+ mDeltaTime = new long[mNumClusters];
+ return true;
+ }
+
private boolean checkPrecondition(ProcFileIterator iter) {
if (iter == null || !iter.hasNextLine()) {
// Error logged in KernelCpuProcStringReader.
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 43df2e444932..57acfad3c15e 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -207,6 +207,7 @@ cc_library_shared {
"com_android_internal_os_AtomicDirectory.cpp",
"com_android_internal_os_ClassLoaderFactory.cpp",
"com_android_internal_os_FuseAppLoop.cpp",
+ "com_android_internal_os_KernelCpuUidBpfMapReader.cpp",
"com_android_internal_os_Zygote.cpp",
"com_android_internal_os_ZygoteInit.cpp",
"com_android_internal_util_VirtualRefBasePtr.cpp",
@@ -303,6 +304,7 @@ cc_library_shared {
"libdl",
"libdl_android",
"libstatslog",
+ "libtimeinstate",
"server_configurable_flags",
],
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 5c3640e3b9a0..49f5d25aca87 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -230,6 +230,7 @@ extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env
extern int register_com_android_internal_os_AtomicDirectory(JNIEnv *env);
extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
+extern int register_com_android_internal_os_KernelCpuUidBpfMapReader(JNIEnv *env);
extern int register_com_android_internal_os_Zygote(JNIEnv *env);
extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env);
extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
@@ -1640,6 +1641,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
REG_JNI(register_com_android_internal_os_AtomicDirectory),
REG_JNI(register_com_android_internal_os_FuseAppLoop),
+ REG_JNI(register_com_android_internal_os_KernelCpuUidBpfMapReader),
};
/*
diff --git a/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp b/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp
new file mode 100644
index 000000000000..7c68de504417
--- /dev/null
+++ b/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#include "core_jni_helpers.h"
+
+#include <sys/sysinfo.h>
+
+#include <android-base/stringprintf.h>
+#include <cputimeinstate.h>
+
+namespace android {
+
+static constexpr uint64_t NSEC_PER_MSEC = 1000000;
+
+static struct {
+ jclass clazz;
+ jmethodID put;
+ jmethodID get;
+} gSparseArrayClassInfo;
+
+static jfieldID gmData;
+
+static jlongArray getUidArray(JNIEnv *env, jobject sparseAr, uint32_t uid, jsize sz) {
+ jlongArray ar = (jlongArray)env->CallObjectMethod(sparseAr, gSparseArrayClassInfo.get, uid);
+ if (!ar) {
+ ar = env->NewLongArray(sz);
+ if (ar == NULL) return ar;
+ env->CallVoidMethod(sparseAr, gSparseArrayClassInfo.put, uid, ar);
+ }
+ return ar;
+}
+
+static void copy2DVecToArray(JNIEnv *env, jlongArray ar, std::vector<std::vector<uint64_t>> &vec) {
+ jsize start = 0;
+ for (auto &subVec : vec) {
+ for (uint32_t i = 0; i < subVec.size(); ++i) subVec[i] /= NSEC_PER_MSEC;
+ env->SetLongArrayRegion(ar, start, subVec.size(),
+ reinterpret_cast<const jlong *>(subVec.data()));
+ start += subVec.size();
+ }
+}
+
+static jboolean KernelCpuUidFreqTimeBpfMapReader_removeUidRange(JNIEnv *env, jclass, jint startUid,
+ jint endUid) {
+ for (uint32_t uid = startUid; uid <= endUid; ++uid) {
+ if (!android::bpf::clearUidTimes(uid)) return false;
+ }
+ return true;
+}
+
+static jboolean KernelCpuUidFreqTimeBpfMapReader_readBpfData(JNIEnv *env, jobject thiz) {
+ static uint64_t lastUpdate = 0;
+ uint64_t newLastUpdate = lastUpdate;
+ auto sparseAr = env->GetObjectField(thiz, gmData);
+ if (sparseAr == NULL) return false;
+ auto data = android::bpf::getUidsUpdatedCpuFreqTimes(&newLastUpdate);
+ if (!data.has_value()) return false;
+
+ jsize s = 0;
+ for (auto &[uid, times] : *data) {
+ if (s == 0) {
+ for (const auto &subVec : times) s += subVec.size();
+ }
+ jlongArray ar = getUidArray(env, sparseAr, uid, s);
+ if (ar == NULL) return false;
+ copy2DVecToArray(env, ar, times);
+ }
+ lastUpdate = newLastUpdate;
+ 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) {
+ static uint64_t lastUpdate = 0;
+ uint64_t newLastUpdate = lastUpdate;
+ auto sparseAr = env->GetObjectField(thiz, gmData);
+ if (sparseAr == NULL) return false;
+ auto data = android::bpf::getUidsUpdatedConcurrentTimes(&newLastUpdate);
+ if (!data.has_value()) return false;
+
+ for (auto &[uid, times] : *data) {
+ // TODO: revise calling code so we can divide by NSEC_PER_MSEC here instead
+ for (auto &time : times.active) time /= NSEC_PER_MSEC;
+ jlongArray ar = getUidArray(env, sparseAr, uid, times.active.size());
+ if (ar == NULL) return false;
+ env->SetLongArrayRegion(ar, 0, times.active.size(),
+ reinterpret_cast<const jlong *>(times.active.data()));
+ }
+ lastUpdate = newLastUpdate;
+ return true;
+}
+
+static jlongArray KernelCpuUidActiveTimeBpfMapReader_getDataDimensions(JNIEnv *env, jobject) {
+ jlong nCpus = get_nprocs_conf();
+
+ auto ar = env->NewLongArray(1);
+ if (ar != NULL) env->SetLongArrayRegion(ar, 0, 1, &nCpus);
+ return ar;
+}
+
+static const JNINativeMethod gActiveTimeMethods[] = {
+ {"readBpfData", "()Z", (void *)KernelCpuUidActiveTimeBpfMapReader_readBpfData},
+ {"getDataDimensions", "()[J", (void *)KernelCpuUidActiveTimeBpfMapReader_getDataDimensions},
+};
+
+static jboolean KernelCpuUidClusterTimeBpfMapReader_readBpfData(JNIEnv *env, jobject thiz) {
+ static uint64_t lastUpdate = 0;
+ uint64_t newLastUpdate = lastUpdate;
+ auto sparseAr = env->GetObjectField(thiz, gmData);
+ if (sparseAr == NULL) return false;
+ auto data = android::bpf::getUidsUpdatedConcurrentTimes(&newLastUpdate);
+ if (!data.has_value()) return false;
+
+ jsize s = 0;
+ for (auto &[uid, times] : *data) {
+ if (s == 0) {
+ for (const auto &subVec : times.policy) s += subVec.size();
+ }
+ jlongArray ar = getUidArray(env, sparseAr, uid, s);
+ if (ar == NULL) return false;
+ copy2DVecToArray(env, ar, times.policy);
+ }
+ lastUpdate = newLastUpdate;
+ return true;
+}
+
+static jlongArray KernelCpuUidClusterTimeBpfMapReader_getDataDimensions(JNIEnv *env, jobject) {
+ auto times = android::bpf::getUidConcurrentTimes(0);
+ if (!times.has_value()) return NULL;
+
+ std::vector<jlong> clusterCores;
+ for (const auto &vec : times->policy) clusterCores.push_back(vec.size());
+ auto ar = env->NewLongArray(clusterCores.size());
+ if (ar != NULL) env->SetLongArrayRegion(ar, 0, clusterCores.size(), clusterCores.data());
+ return ar;
+}
+
+static const JNINativeMethod gClusterTimeMethods[] = {
+ {"readBpfData", "()Z", (void *)KernelCpuUidClusterTimeBpfMapReader_readBpfData},
+ {"getDataDimensions", "()[J",
+ (void *)KernelCpuUidClusterTimeBpfMapReader_getDataDimensions},
+};
+
+struct readerMethods {
+ const char *name;
+ const JNINativeMethod *methods;
+ int numMethods;
+};
+
+static const readerMethods gAllMethods[] = {
+ {"KernelCpuUidFreqTimeBpfMapReader", gFreqTimeMethods, NELEM(gFreqTimeMethods)},
+ {"KernelCpuUidActiveTimeBpfMapReader", gActiveTimeMethods, NELEM(gActiveTimeMethods)},
+ {"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);
+ gSparseArrayClassInfo.put =
+ GetMethodIDOrDie(env, gSparseArrayClassInfo.clazz, "put", "(ILjava/lang/Object;)V");
+ 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;");
+
+ 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);
+ if (ret < 0) break;
+ }
+ return ret;
+}
+
+} // namespace android
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index a6329298b0f9..2ad8e18741f9 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -40,6 +40,7 @@ import org.junit.runners.Suite;
BatteryStatsUserLifecycleTests.class,
KernelCpuProcStringReaderTest.class,
KernelCpuUidActiveTimeReaderTest.class,
+ KernelCpuUidBpfMapReaderTest.class,
KernelCpuUidClusterTimeReaderTest.class,
KernelCpuUidFreqTimeReaderTest.class,
KernelCpuUidUserSysTimeReaderTest.class,
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java
index 1b13a9927beb..2ccd74e99116 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java
@@ -21,11 +21,11 @@ import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.os.FileUtils;
+import android.util.SparseArray;
import android.util.SparseLongArray;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
@@ -33,11 +33,15 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Random;
/**
@@ -46,14 +50,16 @@ import java.util.Random;
* $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuUidActiveTimeReaderTest
*/
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
public class KernelCpuUidActiveTimeReaderTest {
private File mTestDir;
private File mTestFile;
private KernelCpuUidActiveTimeReader mReader;
+ private KernelCpuUidTestBpfMapReader mBpfMapReader;
private VerifiableCallback mCallback;
private Random mRand = new Random(12345);
+ protected boolean mUseBpf;
private final int mCpus = 4;
private final String mHeadline = "cpus: 4\n";
private final int[] mUids = {0, 1, 22, 333, 4444, 55555};
@@ -62,12 +68,22 @@ public class KernelCpuUidActiveTimeReaderTest {
return InstrumentationRegistry.getContext();
}
+ @Parameters(name="useBpf={0}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] { {true}, {false} });
+ }
+
+ public KernelCpuUidActiveTimeReaderTest(boolean useBpf) {
+ mUseBpf = useBpf;
+ }
+
@Before
public void setUp() {
mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
mTestFile = new File(mTestDir, "test.file");
+ mBpfMapReader = new KernelCpuUidTestBpfMapReader();
mReader = new KernelCpuUidActiveTimeReader(
- new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+ new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader, false);
mCallback = new VerifiableCallback();
}
@@ -80,7 +96,7 @@ public class KernelCpuUidActiveTimeReaderTest {
@Test
public void testReadDelta() throws Exception {
final long[][] times = increaseTime(new long[mUids.length][mCpus]);
- writeToFile(mHeadline + uidLines(mUids, times));
+ setCpusAndData(times);
mReader.readDelta(mCallback);
for (int i = 0; i < mUids.length; ++i) {
mCallback.verify(mUids[i], getActiveTime(times[i]));
@@ -90,7 +106,7 @@ public class KernelCpuUidActiveTimeReaderTest {
// Verify that a second call will only return deltas.
mCallback.clear();
final long[][] newTimes1 = increaseTime(times);
- writeToFile(mHeadline + uidLines(mUids, newTimes1));
+ setCpusAndData(newTimes1);
mReader.readDelta(mCallback);
for (int i = 0; i < mUids.length; ++i) {
mCallback.verify(mUids[i], getActiveTime(newTimes1[i]) - getActiveTime(times[i]));
@@ -105,7 +121,7 @@ public class KernelCpuUidActiveTimeReaderTest {
// Verify that calling with a null callback doesn't result in any crashes
mCallback.clear();
final long[][] newTimes2 = increaseTime(newTimes1);
- writeToFile(mHeadline + uidLines(mUids, newTimes2));
+ setCpusAndData(newTimes2);
mReader.readDelta(null);
mCallback.verifyNoMoreInteractions();
@@ -113,19 +129,20 @@ public class KernelCpuUidActiveTimeReaderTest {
// the previous call had null callback.
mCallback.clear();
final long[][] newTimes3 = increaseTime(newTimes2);
+ setCpusAndData(newTimes3);
writeToFile(mHeadline + uidLines(mUids, newTimes3));
mReader.readDelta(mCallback);
for (int i = 0; i < mUids.length; ++i) {
mCallback.verify(mUids[i], getActiveTime(newTimes3[i]) - getActiveTime(newTimes2[i]));
}
mCallback.verifyNoMoreInteractions();
- assertTrue(mTestFile.delete());
+ clearCpusAndData();
}
@Test
public void testReadAbsolute() throws Exception {
final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
- writeToFile(mHeadline + uidLines(mUids, times1));
+ setCpusAndData(times1);
mReader.readAbsolute(mCallback);
for (int i = 0; i < mUids.length; i++) {
mCallback.verify(mUids[i], getActiveTime(times1[i]));
@@ -135,19 +152,19 @@ public class KernelCpuUidActiveTimeReaderTest {
// Verify that a second call should still return absolute values
mCallback.clear();
final long[][] times2 = increaseTime(times1);
- writeToFile(mHeadline + uidLines(mUids, times2));
+ setCpusAndData(times2);
mReader.readAbsolute(mCallback);
for (int i = 0; i < mUids.length; i++) {
mCallback.verify(mUids[i], getActiveTime(times2[i]));
}
mCallback.verifyNoMoreInteractions();
- assertTrue(mTestFile.delete());
+ clearCpusAndData();
}
@Test
public void testReadDeltaDecreasedTime() throws Exception {
final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
- writeToFile(mHeadline + uidLines(mUids, times1));
+ setCpusAndData(times1);
mReader.readDelta(mCallback);
// Verify that there should not be a callback for a particular UID if its time decreases.
@@ -155,19 +172,19 @@ public class KernelCpuUidActiveTimeReaderTest {
final long[][] times2 = increaseTime(times1);
System.arraycopy(times1[0], 0, times2[0], 0, mCpus);
times2[0][0] = 100;
- writeToFile(mHeadline + uidLines(mUids, times2));
+ setCpusAndData(times2);
mReader.readDelta(mCallback);
for (int i = 1; i < mUids.length; i++) {
mCallback.verify(mUids[i], getActiveTime(times2[i]) - getActiveTime(times1[i]));
}
mCallback.verifyNoMoreInteractions();
- assertTrue(mTestFile.delete());
+ clearCpusAndData();
// Verify that the internal state was not modified.
mCallback.clear();
final long[][] times3 = increaseTime(times2);
times3[0] = increaseTime(times1)[0];
- writeToFile(mHeadline + uidLines(mUids, times3));
+ setCpusAndData(times3);
mReader.readDelta(mCallback);
mCallback.verify(mUids[0], getActiveTime(times3[0]) - getActiveTime(times1[0]));
for (int i = 1; i < mUids.length; i++) {
@@ -179,26 +196,26 @@ public class KernelCpuUidActiveTimeReaderTest {
@Test
public void testReadDeltaNegativeTime() throws Exception {
final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
- writeToFile(mHeadline + uidLines(mUids, times1));
+ setCpusAndData(times1);
mReader.readDelta(mCallback);
// Verify that there should not be a callback for a particular UID if its time is -ve.
mCallback.clear();
final long[][] times2 = increaseTime(times1);
times2[0][0] *= -1;
- writeToFile(mHeadline + uidLines(mUids, times2));
+ setCpusAndData(times2);
mReader.readDelta(mCallback);
for (int i = 1; i < mUids.length; i++) {
mCallback.verify(mUids[i], getActiveTime(times2[i]) - getActiveTime(times1[i]));
}
mCallback.verifyNoMoreInteractions();
- assertTrue(mTestFile.delete());
+ clearCpusAndData();
// Verify that the internal state was not modified.
mCallback.clear();
final long[][] times3 = increaseTime(times2);
times3[0] = increaseTime(times1)[0];
- writeToFile(mHeadline + uidLines(mUids, times3));
+ setCpusAndData(times3);
mReader.readDelta(mCallback);
mCallback.verify(mUids[0], getActiveTime(times3[0]) - getActiveTime(times1[0]));
for (int i = 1; i < mUids.length; i++) {
@@ -207,6 +224,28 @@ public class KernelCpuUidActiveTimeReaderTest {
mCallback.verifyNoMoreInteractions();
}
+ private void setCpusAndData(long[][] times) throws IOException {
+ if (mUseBpf) {
+ mBpfMapReader.setCpus(new long[]{ mCpus });
+ SparseArray<long[]> data = new SparseArray<>();
+ for (int i = 0; i < mUids.length; i++) {
+ data.put(mUids[i], times[i]);
+ }
+ mBpfMapReader.setData(data);
+ } else {
+ writeToFile(mHeadline + uidLines(mUids, times));
+ }
+ }
+
+ private void clearCpusAndData() {
+ if (mUseBpf) {
+ mBpfMapReader.setCpus(null);
+ mBpfMapReader.setData(new SparseArray<>());
+ } else {
+ assertTrue(mTestFile.delete());
+ }
+ }
+
private String uidLines(int[] uids, long[][] times) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < uids.length; i++) {
@@ -261,4 +300,36 @@ public class KernelCpuUidActiveTimeReaderTest {
assertEquals(0, mData.size());
}
}
+
+ private class KernelCpuUidTestBpfMapReader extends KernelCpuUidBpfMapReader {
+ private long[] mCpus;
+ private SparseArray<long[]> mNewData = new SparseArray<>();
+
+ public void setData(SparseArray<long[]> data) {
+ mNewData = data;
+ }
+
+ public void setCpus(long[] cpus) {
+ mCpus = cpus;
+ }
+
+ @Override
+ public final boolean startTrackingBpfTimes() {
+ return true;
+ }
+
+ @Override
+ protected final boolean readBpfData() {
+ if (!mUseBpf) {
+ return false;
+ }
+ mData = mNewData;
+ return true;
+ }
+
+ @Override
+ public final long[] getDataDimensions() {
+ return mCpus;
+ }
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java
new file mode 100644
index 000000000000..257b388917e4
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2020 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.internal.os;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.util.SparseArray;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.runner.RunWith;
+
+import com.android.internal.os.KernelCpuUidBpfMapReader.BpfMapIterator;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.IntStream;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KernelCpuUidBpfMapReaderTest {
+ private Random mRand = new Random(12345);
+ private KernelCpuUidTestBpfMapReader mReader;
+
+ private Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+
+ @Before
+ public void setUp() {
+ mReader = new KernelCpuUidTestBpfMapReader();
+ }
+
+ /**
+ * Tests that reading returns null if readBpfData() fails.
+ */
+ @Test
+ public void testUnsuccessfulRead() {
+ assertEquals(null, mReader.open());
+ }
+
+ /**
+ * Tests that reading will always return null after 5 failures.
+ */
+ @Test
+ public void testReadErrorsLimit() {
+ for (int i = 0; i < 3; i++) {
+ try (BpfMapIterator iter = mReader.open()) {
+ assertNull(iter);
+ }
+ }
+
+ SparseArray<long[]> data = new SparseArray<>();
+ long[] times = {2};
+ data.put(1, new long[]{2});
+ mReader.setData(data);
+ testOpenAndReadData(data);
+
+ mReader.setData(null);
+ for (int i = 0; i < 3; i++) {
+ try (BpfMapIterator iter = mReader.open(true)) {
+ assertNull(iter);
+ }
+ }
+ mReader.setData(data);
+ try (BpfMapIterator iter = mReader.open(true)) {
+ assertNull(iter);
+ }
+ }
+
+ /** Tests getNextUid functionality. */
+ @Test
+ public void testGetNextUid() {
+ final SparseArray<long[]> data = getTestSparseArray(800, 50);
+ mReader.setData(data);
+ testOpenAndReadData(data);
+ }
+
+ @Test
+ public void testConcurrent() throws Exception {
+ final SparseArray<long[]> data = getTestSparseArray(200, 50);
+ final SparseArray<long[]> data1 = getTestSparseArray(180, 70);
+ final List<Throwable> errs = Collections.synchronizedList(new ArrayList<>());
+ mReader.setData(data);
+ // An additional thread for modifying the data.
+ ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(11);
+ final CountDownLatch ready = new CountDownLatch(10);
+ final CountDownLatch start = new CountDownLatch(1);
+ final CountDownLatch modify = new CountDownLatch(1);
+ final CountDownLatch done = new CountDownLatch(10);
+
+ for (int i = 0; i < 5; i++) {
+ threadPool.submit(() -> {
+ ready.countDown();
+ try {
+ start.await();
+ testOpenAndReadData(data);
+ } catch (Throwable e) {
+ errs.add(e);
+ } finally {
+ done.countDown();
+ }
+ });
+ threadPool.submit(() -> {
+ ready.countDown();
+ try {
+ start.await();
+ // Wait for data modification.
+ modify.await();
+ testOpenAndReadData(data1);
+ } catch (Throwable e) {
+ errs.add(e);
+ } finally {
+ done.countDown();
+ }
+ });
+ }
+
+ assertTrue("Prep timed out", ready.await(100, TimeUnit.MILLISECONDS));
+ start.countDown();
+
+ threadPool.schedule(() -> {
+ mReader.setData(data1);
+ modify.countDown();
+ }, 600, TimeUnit.MILLISECONDS);
+
+ assertTrue("Execution timed out", done.await(3, TimeUnit.SECONDS));
+ threadPool.shutdownNow();
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ errs.forEach(e -> e.printStackTrace(pw));
+
+ assertTrue("All Exceptions:\n" + sw.toString(), errs.isEmpty());
+ }
+
+ @Test
+ public void testRemoveUidsInRange() {
+ final SparseArray<long[]> data = getTestSparseArray(200, 50);
+ mReader.setData(data);
+ testOpenAndReadData(data);
+ SparseArray<long[]> changedData = new SparseArray<>();
+ for (int i = 6; i < 200; i++) {
+ changedData.put(i, data.get(i));
+ }
+ mReader.removeUidsInRange(0, 5);
+ testOpenAndReadData(changedData);
+ }
+
+ private void testOpenAndReadData(SparseArray<long[]> expectedData) {
+ try (BpfMapIterator iter = mReader.open()) {
+ long[] actual;
+ if (expectedData.size() > 0) {
+ actual = new long[expectedData.valueAt(0).length + 1];
+ } else {
+ actual = new long[0];
+ }
+ for (int i = 0; i < expectedData.size(); i++) {
+ assertTrue(iter.getNextUid(actual));
+ assertEquals(expectedData.keyAt(i), actual[0]);
+ assertArrayEquals(expectedData.valueAt(i), Arrays.copyOfRange(actual, 1, actual.length));
+ }
+ assertFalse(iter.getNextUid(actual));
+ assertFalse(iter.getNextUid(actual));
+ }
+ }
+
+
+ private SparseArray<long[]> getTestSparseArray(int uids, int numPerUid) {
+ SparseArray<long[]> data = new SparseArray<>();
+ for (int i = 0; i < uids; i++) {
+ data.put(i, mRand.longs(numPerUid, 0, Long.MAX_VALUE).toArray());
+ }
+ return data;
+ }
+
+
+ private class KernelCpuUidTestBpfMapReader extends KernelCpuUidBpfMapReader {
+ private SparseArray<long[]> mNewData;
+
+ public final void setData(SparseArray<long[]> newData) {
+ mNewData = newData;
+ }
+
+ @Override
+ public final boolean startTrackingBpfTimes() {
+ return true;
+ }
+
+ @Override
+ public final boolean readBpfData() {
+ if (mNewData == null) {
+ return false;
+ }
+ mData = mNewData;
+ return true;
+ }
+
+ @Override
+ public final long[] getDataDimensions() {
+ return null;
+ }
+
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java
index 2ea80da9e6d0..a0dab28a6da5 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java
@@ -27,7 +27,6 @@ import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
@@ -35,11 +34,15 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Random;
/**
@@ -48,28 +51,42 @@ import java.util.Random;
* $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuUidClusterTimeReaderTest
*/
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
public class KernelCpuUidClusterTimeReaderTest {
private File mTestDir;
private File mTestFile;
private KernelCpuUidClusterTimeReader mReader;
+ private KernelCpuUidTestBpfMapReader mBpfMapReader;
private VerifiableCallback mCallback;
private Random mRand = new Random(12345);
+ protected boolean mUseBpf;
private final int mCpus = 6;
private final String mHeadline = "policy0: 4 policy4: 2\n";
+ private final long[] mCores = {4, 2};
private final int[] mUids = {0, 1, 22, 333, 4444, 55555};
private Context getContext() {
return InstrumentationRegistry.getContext();
}
+ @Parameters(name="useBpf={0}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] { {true}, {false} });
+ }
+
+ public KernelCpuUidClusterTimeReaderTest(boolean useBpf) {
+ mUseBpf = useBpf;
+ }
+
@Before
public void setUp() {
mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
mTestFile = new File(mTestDir, "test.file");
+ mBpfMapReader = new KernelCpuUidTestBpfMapReader();
mReader = new KernelCpuUidClusterTimeReader(
- new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+ new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader,
+ false);
mCallback = new VerifiableCallback();
}
@@ -82,7 +99,7 @@ public class KernelCpuUidClusterTimeReaderTest {
@Test
public void testReadDelta() throws Exception {
final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
- writeToFile(mHeadline + uidLines(mUids, times1));
+ setCoresAndData(times1);
mReader.readDelta(mCallback);
for (int i = 0; i < mUids.length; ++i) {
mCallback.verify(mUids[i], clusterTime(times1[i]));
@@ -92,7 +109,7 @@ public class KernelCpuUidClusterTimeReaderTest {
// Verify that a second call will only return deltas.
mCallback.clear();
final long[][] times2 = increaseTime(times1);
- writeToFile(mHeadline + uidLines(mUids, times2));
+ setCoresAndData(times2);
mReader.readDelta(mCallback);
for (int i = 0; i < mUids.length; ++i) {
mCallback.verify(mUids[i], subtract(clusterTime(times2[i]), clusterTime(times1[i])));
@@ -107,7 +124,7 @@ public class KernelCpuUidClusterTimeReaderTest {
// Verify that calling with a null callback doesn't result in any crashes
mCallback.clear();
final long[][] times3 = increaseTime(times2);
- writeToFile(mHeadline + uidLines(mUids, times3));
+ setCoresAndData(times3);
mReader.readDelta(null);
mCallback.verifyNoMoreInteractions();
@@ -115,19 +132,19 @@ public class KernelCpuUidClusterTimeReaderTest {
// the previous call had null callback.
mCallback.clear();
final long[][] times4 = increaseTime(times3);
- writeToFile(mHeadline + uidLines(mUids, times4));
+ setCoresAndData(times4);
mReader.readDelta(mCallback);
for (int i = 0; i < mUids.length; ++i) {
mCallback.verify(mUids[i], subtract(clusterTime(times4[i]), clusterTime(times3[i])));
}
mCallback.verifyNoMoreInteractions();
- assertTrue(mTestFile.delete());
+ clearCoresAndData();
}
@Test
public void testReadAbsolute() throws Exception {
final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
- writeToFile(mHeadline + uidLines(mUids, times1));
+ setCoresAndData(times1);
mReader.readAbsolute(mCallback);
for (int i = 0; i < mUids.length; i++) {
mCallback.verify(mUids[i], clusterTime(times1[i]));
@@ -137,19 +154,19 @@ public class KernelCpuUidClusterTimeReaderTest {
// Verify that a second call should still return absolute values
mCallback.clear();
final long[][] times2 = increaseTime(times1);
- writeToFile(mHeadline + uidLines(mUids, times2));
+ setCoresAndData(times2);
mReader.readAbsolute(mCallback);
for (int i = 0; i < mUids.length; i++) {
mCallback.verify(mUids[i], clusterTime(times2[i]));
}
mCallback.verifyNoMoreInteractions();
- assertTrue(mTestFile.delete());
+ clearCoresAndData();
}
@Test
public void testReadDeltaDecreasedTime() throws Exception {
final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
- writeToFile(mHeadline + uidLines(mUids, times1));
+ setCoresAndData(times1);
mReader.readDelta(mCallback);
// Verify that there should not be a callback for a particular UID if its time decreases.
@@ -157,19 +174,19 @@ public class KernelCpuUidClusterTimeReaderTest {
final long[][] times2 = increaseTime(times1);
System.arraycopy(times1[0], 0, times2[0], 0, mCpus);
times2[0][0] = 100;
- writeToFile(mHeadline + uidLines(mUids, times2));
+ setCoresAndData(times2);
mReader.readDelta(mCallback);
for (int i = 1; i < mUids.length; i++) {
mCallback.verify(mUids[i], subtract(clusterTime(times2[i]), clusterTime(times1[i])));
}
mCallback.verifyNoMoreInteractions();
- assertTrue(mTestFile.delete());
+ clearCoresAndData();
// Verify that the internal state was not modified.
mCallback.clear();
final long[][] times3 = increaseTime(times2);
times3[0] = increaseTime(times1)[0];
- writeToFile(mHeadline + uidLines(mUids, times3));
+ setCoresAndData(times3);
mReader.readDelta(mCallback);
mCallback.verify(mUids[0], subtract(clusterTime(times3[0]), clusterTime(times1[0])));
for (int i = 1; i < mUids.length; i++) {
@@ -181,26 +198,26 @@ public class KernelCpuUidClusterTimeReaderTest {
@Test
public void testReadDeltaNegativeTime() throws Exception {
final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
- writeToFile(mHeadline + uidLines(mUids, times1));
+ setCoresAndData(times1);
mReader.readDelta(mCallback);
// Verify that there should not be a callback for a particular UID if its time decreases.
mCallback.clear();
final long[][] times2 = increaseTime(times1);
times2[0][0] *= -1;
- writeToFile(mHeadline + uidLines(mUids, times2));
+ setCoresAndData(times2);
mReader.readDelta(mCallback);
for (int i = 1; i < mUids.length; i++) {
mCallback.verify(mUids[i], subtract(clusterTime(times2[i]), clusterTime(times1[i])));
}
mCallback.verifyNoMoreInteractions();
- assertTrue(mTestFile.delete());
+ clearCoresAndData();
// Verify that the internal state was not modified.
mCallback.clear();
final long[][] times3 = increaseTime(times2);
times3[0] = increaseTime(times1)[0];
- writeToFile(mHeadline + uidLines(mUids, times3));
+ setCoresAndData(times3);
mReader.readDelta(mCallback);
mCallback.verify(mUids[0], subtract(clusterTime(times3[0]), clusterTime(times1[0])));
for (int i = 1; i < mUids.length; i++) {
@@ -209,6 +226,28 @@ public class KernelCpuUidClusterTimeReaderTest {
mCallback.verifyNoMoreInteractions();
}
+ private void setCoresAndData(long[][] times) throws IOException {
+ if (mUseBpf) {
+ mBpfMapReader.setClusterCores(mCores);
+ SparseArray<long[]> data = new SparseArray<>();
+ for (int i = 0; i < mUids.length; i++) {
+ data.put(mUids[i], times[i]);
+ }
+ mBpfMapReader.setData(data);
+ } else {
+ writeToFile(mHeadline + uidLines(mUids, times));
+ }
+ }
+
+ private void clearCoresAndData() {
+ if (mUseBpf) {
+ mBpfMapReader.setClusterCores(null);
+ mBpfMapReader.setData(new SparseArray<>());
+ } else {
+ assertTrue(mTestFile.delete());
+ }
+ }
+
private long[] clusterTime(long[] times) {
// Assumes 4 + 2 cores
return new long[]{times[0] + times[1] / 2 + times[2] / 3 + times[3] / 4,
@@ -277,4 +316,36 @@ public class KernelCpuUidClusterTimeReaderTest {
assertEquals(0, mData.size());
}
}
+
+ private class KernelCpuUidTestBpfMapReader extends KernelCpuUidBpfMapReader {
+ private long[] mClusterCores;
+ private SparseArray<long[]> mNewData = new SparseArray<>();
+
+ public void setData(SparseArray<long[]> data) {
+ mNewData = data;
+ }
+
+ public void setClusterCores(long[] cores) {
+ mClusterCores = cores;
+ }
+
+ @Override
+ public final boolean startTrackingBpfTimes() {
+ return true;
+ }
+
+ @Override
+ protected final boolean readBpfData() {
+ if (!mUseBpf) {
+ return false;
+ }
+ mData = mNewData;
+ return true;
+ }
+
+ @Override
+ public final long[] getDataDimensions() {
+ return mClusterCores;
+ }
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java
index 0b6fed386117..c60a6d61ffa1 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java
@@ -29,7 +29,6 @@ import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
@@ -37,6 +36,8 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -45,6 +46,7 @@ import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Random;
/**
@@ -53,14 +55,16 @@ import java.util.Random;
* $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuUidFreqTimeReaderTest
*/
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
public class KernelCpuUidFreqTimeReaderTest {
private File mTestDir;
private File mTestFile;
private KernelCpuUidFreqTimeReader mReader;
+ private KernelCpuUidTestBpfMapReader mBpfMapReader;
private VerifiableCallback mCallback;
@Mock
private PowerProfile mPowerProfile;
+ private boolean mUseBpf;
private Random mRand = new Random(12345);
private final int[] mUids = {0, 1, 22, 333, 4444, 55555};
@@ -69,13 +73,23 @@ public class KernelCpuUidFreqTimeReaderTest {
return InstrumentationRegistry.getContext();
}
+ @Parameters(name="useBpf={0}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] { {true}, {false} });
+ }
+
+ public KernelCpuUidFreqTimeReaderTest(boolean useBpf) {
+ mUseBpf = useBpf;
+ }
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
mTestFile = new File(mTestDir, "test.file");
+ mBpfMapReader = new KernelCpuUidTestBpfMapReader();
mReader = new KernelCpuUidFreqTimeReader(mTestFile.getAbsolutePath(),
- new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+ new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader, false);
mCallback = new VerifiableCallback();
}
@@ -97,17 +111,17 @@ public class KernelCpuUidFreqTimeReaderTest {
final int[][] numFreqs = {{3, 6}, {4, 5}, {3, 5, 4}, {3}};
for (int i = 0; i < freqs.length; ++i) {
mReader = new KernelCpuUidFreqTimeReader(mTestFile.getAbsolutePath(),
- new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+ new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader, false);
setCpuClusterFreqs(numClusters[i], numFreqs[i]);
- writeToFile(freqsLine(freqs[i]));
+ setFreqs(freqs[i]);
long[] actualFreqs = mReader.readFreqs(mPowerProfile);
assertArrayEquals(freqs[i], actualFreqs);
final String errMsg = String.format("Freqs=%s, nClusters=%d, nFreqs=%s",
Arrays.toString(freqs[i]), numClusters[i], Arrays.toString(numFreqs[i]));
assertFalse(errMsg, mReader.perClusterTimesAvailable());
- // Verify that a second call won't read the proc file again
- assertTrue(mTestFile.delete());
+ // Verify that a second call won't re-read the freqs
+ clearFreqsAndData();
actualFreqs = mReader.readFreqs(mPowerProfile);
assertArrayEquals(freqs[i], actualFreqs);
assertFalse(errMsg, mReader.perClusterTimesAvailable());
@@ -125,17 +139,17 @@ public class KernelCpuUidFreqTimeReaderTest {
final int[][] numFreqs = {{4}, {3, 5}, {3, 5, 4}};
for (int i = 0; i < freqs.length; ++i) {
mReader = new KernelCpuUidFreqTimeReader(mTestFile.getAbsolutePath(),
- new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+ new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader, false);
setCpuClusterFreqs(numClusters[i], numFreqs[i]);
- writeToFile(freqsLine(freqs[i]));
+ setFreqs(freqs[i]);
long[] actualFreqs = mReader.readFreqs(mPowerProfile);
assertArrayEquals(freqs[i], actualFreqs);
final String errMsg = String.format("Freqs=%s, nClusters=%d, nFreqs=%s",
Arrays.toString(freqs[i]), numClusters[i], Arrays.toString(numFreqs[i]));
assertTrue(errMsg, mReader.perClusterTimesAvailable());
- // Verify that a second call won't read the proc file again
- assertTrue(mTestFile.delete());
+ // Verify that a second call won't re-read the freqs
+ clearFreqsAndData();
actualFreqs = mReader.readFreqs(mPowerProfile);
assertArrayEquals(freqs[i], actualFreqs);
assertTrue(errMsg, mReader.perClusterTimesAvailable());
@@ -147,7 +161,7 @@ public class KernelCpuUidFreqTimeReaderTest {
final long[] freqs = {110, 123, 145, 167, 289, 997};
final long[][] times = increaseTime(new long[mUids.length][freqs.length]);
- writeToFile(freqsLine(freqs) + uidLines(mUids, times));
+ setFreqsAndData(freqs, times);
mReader.readDelta(mCallback);
for (int i = 0; i < mUids.length; ++i) {
mCallback.verify(mUids[i], times[i]);
@@ -155,14 +169,14 @@ public class KernelCpuUidFreqTimeReaderTest {
mCallback.verifyNoMoreInteractions();
// Verify that readDelta also reads the frequencies if not already available.
- assertTrue(mTestFile.delete());
+ clearFreqsAndData();
long[] actualFreqs = mReader.readFreqs(mPowerProfile);
assertArrayEquals(freqs, actualFreqs);
// Verify that a second call will only return deltas.
mCallback.clear();
final long[][] newTimes1 = increaseTime(times);
- writeToFile(freqsLine(freqs) + uidLines(mUids, newTimes1));
+ setFreqsAndData(freqs, newTimes1);
mReader.readDelta(mCallback);
for (int i = 0; i < mUids.length; ++i) {
mCallback.verify(mUids[i], subtract(newTimes1[i], times[i]));
@@ -177,7 +191,7 @@ public class KernelCpuUidFreqTimeReaderTest {
// Verify that calling with a null callback doesn't result in any crashes
mCallback.clear();
final long[][] newTimes2 = increaseTime(newTimes1);
- writeToFile(freqsLine(freqs) + uidLines(mUids, newTimes2));
+ setFreqsAndData(freqs, newTimes2);
mReader.readDelta(null);
mCallback.verifyNoMoreInteractions();
@@ -185,13 +199,13 @@ public class KernelCpuUidFreqTimeReaderTest {
// the previous call had null callback.
mCallback.clear();
final long[][] newTimes3 = increaseTime(newTimes2);
- writeToFile(freqsLine(freqs) + uidLines(mUids, newTimes3));
+ setFreqsAndData(freqs, newTimes3);
mReader.readDelta(mCallback);
for (int i = 0; i < mUids.length; ++i) {
mCallback.verify(mUids[i], subtract(newTimes3[i], newTimes2[i]));
}
mCallback.verifyNoMoreInteractions();
- assertTrue(mTestFile.delete());
+ clearFreqsAndData();
}
@Test
@@ -199,7 +213,7 @@ public class KernelCpuUidFreqTimeReaderTest {
final long[] freqs = {110, 123, 145, 167, 289, 997};
final long[][] times1 = increaseTime(new long[mUids.length][freqs.length]);
- writeToFile(freqsLine(freqs) + uidLines(mUids, times1));
+ setFreqsAndData(freqs, times1);
mReader.readAbsolute(mCallback);
for (int i = 0; i < mUids.length; i++) {
mCallback.verify(mUids[i], times1[i]);
@@ -207,20 +221,20 @@ public class KernelCpuUidFreqTimeReaderTest {
mCallback.verifyNoMoreInteractions();
// Verify that readDelta also reads the frequencies if not already available.
- assertTrue(mTestFile.delete());
+ clearFreqsAndData();
long[] actualFreqs = mReader.readFreqs(mPowerProfile);
assertArrayEquals(freqs, actualFreqs);
// Verify that a second call should still return absolute values
mCallback.clear();
final long[][] times2 = increaseTime(times1);
- writeToFile(freqsLine(freqs) + uidLines(mUids, times2));
+ setFreqsAndData(freqs, times2);
mReader.readAbsolute(mCallback);
for (int i = 0; i < mUids.length; i++) {
mCallback.verify(mUids[i], times2[i]);
}
mCallback.verifyNoMoreInteractions();
- assertTrue(mTestFile.delete());
+ clearFreqsAndData();
}
@Test
@@ -228,14 +242,14 @@ public class KernelCpuUidFreqTimeReaderTest {
final long[] freqs = {110, 123, 145, 167, 289, 997};
final long[][] times1 = increaseTime(new long[mUids.length][freqs.length]);
- writeToFile(freqsLine(freqs) + uidLines(mUids, times1));
+ setFreqsAndData(freqs, times1);
mReader.readDelta(mCallback);
// Verify that there should not be a callback for a particular UID if its time decreases.
mCallback.clear();
final long[][] times2 = increaseTime(times1);
times2[0][0] = 1000;
- writeToFile(freqsLine(freqs) + uidLines(mUids, times2));
+ setFreqsAndData(freqs, times2);
mReader.readDelta(mCallback);
for (int i = 1; i < mUids.length; i++) {
mCallback.verify(mUids[i], subtract(times2[i], times1[i]));
@@ -246,7 +260,7 @@ public class KernelCpuUidFreqTimeReaderTest {
mCallback.clear();
final long[][] times3 = increaseTime(times2);
times3[0] = increaseTime(times1)[0];
- writeToFile(freqsLine(freqs) + uidLines(mUids, times3));
+ setFreqsAndData(freqs, times3);
mReader.readDelta(mCallback);
mCallback.verify(mUids[0], subtract(times3[0], times1[0]));
for (int i = 1; i < mUids.length; i++) {
@@ -258,7 +272,7 @@ public class KernelCpuUidFreqTimeReaderTest {
mCallback.clear();
final long[][] times4 = increaseTime(times3);
times4[0][0] *= -1;
- writeToFile(freqsLine(freqs) + uidLines(mUids, times4));
+ setFreqsAndData(freqs, times4);
mReader.readDelta(mCallback);
for (int i = 1; i < mUids.length; ++i) {
mCallback.verify(mUids[i], subtract(times4[i], times3[i]));
@@ -269,14 +283,44 @@ public class KernelCpuUidFreqTimeReaderTest {
mCallback.clear();
final long[][] times5 = increaseTime(times4);
times5[0] = increaseTime(times3)[0];
- writeToFile(freqsLine(freqs) + uidLines(mUids, times5));
+ setFreqsAndData(freqs, times5);
mReader.readDelta(mCallback);
mCallback.verify(mUids[0], subtract(times5[0], times3[0]));
for (int i = 1; i < mUids.length; i++) {
mCallback.verify(mUids[i], subtract(times5[i], times4[i]));
}
- assertTrue(mTestFile.delete());
+ clearFreqsAndData();
+ }
+
+ private void setFreqs(long[] freqs) throws IOException {
+ if (mUseBpf) {
+ mBpfMapReader.setFreqs(freqs);
+ } else {
+ writeToFile(freqsLine(freqs));
+ }
+ }
+
+ private void setFreqsAndData(long[] freqs, long[][] times) throws IOException {
+ if (mUseBpf) {
+ mBpfMapReader.setFreqs(freqs);
+ SparseArray<long[]> data = new SparseArray<>();
+ for (int i = 0; i < mUids.length; i++) {
+ data.put(mUids[i], times[i]);
+ }
+ mBpfMapReader.setData(data);
+ } else {
+ writeToFile(freqsLine(freqs) + uidLines(mUids, times));
+ }
+ }
+
+ private void clearFreqsAndData() {
+ if (mUseBpf) {
+ mBpfMapReader.setFreqs(null);
+ mBpfMapReader.setData(new SparseArray<>());
+ } else {
+ assertTrue(mTestFile.delete());
+ }
}
private String freqsLine(long[] freqs) {
@@ -358,4 +402,36 @@ public class KernelCpuUidFreqTimeReaderTest {
assertEquals(0, mData.size());
}
}
+
+ private class KernelCpuUidTestBpfMapReader extends KernelCpuUidBpfMapReader {
+ private long[] mCpuFreqs;
+ private SparseArray<long[]> mNewData = new SparseArray<>();
+
+ public void setData(SparseArray<long[]> data) {
+ mNewData = data;
+ }
+
+ public void setFreqs(long[] freqs) {
+ mCpuFreqs = freqs;
+ }
+
+ @Override
+ public final boolean startTrackingBpfTimes() {
+ return true;
+ }
+
+ @Override
+ protected final boolean readBpfData() {
+ if (!mUseBpf) {
+ return false;
+ }
+ mData = mNewData;
+ return true;
+ }
+
+ @Override
+ public final long[] getDataDimensions() {
+ return mCpuFreqs;
+ }
+ }
}