Use bpf data when available for per-UID cpu stats

Update KernelCpuUidTimeReader and its subclasses to support reading
frequency, active & cluster times from BPF maps rather than proc files
on devices that support this approach. BPF-based accounting offers
improved accuracy as it can detect every context switch, whereas the
proc files attribute a full tick period to whatever happens to be
running when the tick occurs.
Add a KernelCpuUidBpfMapReader class modeled on
KernelCpuProcStringReader, with singletons for reading each distinct
set of data. These follow the locking approach used by
KernelCpuProcStringReader to ensure thread safety, but they collect
data by calling libtimeinstate functions via JNI rather than reading
text from proc files. They also provide a getDataDimensions() function
to retrieve information currently provided by the header rows of the
proc files, such as the list of available freqs or the number of cores
on each cluster.
Extend the KernelCpu*TimeReaderTest classes to exercise the BPF path
for each reader class, and add a KernelCpuUidBpfMapReaderTest modeled
on KernelProcStringReaderTest.

Bug: 138317993
Test: KernelCpu*TimeReaderTests and KernelCpuUidBpfMapReaderTest pass
Test: no regressions in BatteryStatsTests
Change-Id: Ia092e896028e5f647f6c182de05fa76c7e2e3180
Merged-In: Ie7c0b11a47289e16f72fd6868de4185e858c7e4f
Signed-off-by: Connor O'Brien <connoro@google.com>
This commit is contained in:
Connor O'Brien
2019-09-12 14:09:26 -07:00
parent 617b4b00e3
commit 57635c976b
10 changed files with 1207 additions and 155 deletions

View File

@@ -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();
}
}

View File

@@ -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,11 +539,24 @@ 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) {
if (iter == null || !iter.hasNextLine()) {
// Error logged in KernelCpuProcStringReader.
@@ -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.

View File

@@ -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",
],

View File

@@ -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);
@@ -1650,6 +1651,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),
};
/*

View File

@@ -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

View File

@@ -40,6 +40,7 @@ import org.junit.runners.Suite;
BatteryStatsUserLifecycleTests.class,
KernelCpuProcStringReaderTest.class,
KernelCpuUidActiveTimeReaderTest.class,
KernelCpuUidBpfMapReaderTest.class,
KernelCpuUidClusterTimeReaderTest.class,
KernelCpuUidFreqTimeReaderTest.class,
KernelCpuUidUserSysTimeReaderTest.class,

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}