diff options
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; + } + } } |