Merge changes Ie5fa2605,Ia092e896

* changes:
  Use bpf data when available for single-UID cpu stats
  Use bpf data when available for per-UID cpu stats
This commit is contained in:
Connor O'Brien
2020-02-20 22:45:53 +00:00
committed by Gerrit Code Review
13 changed files with 1306 additions and 156 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

@@ -53,6 +53,8 @@ public class KernelSingleUidTimeReader {
private int mReadErrorCounter;
@GuardedBy("this")
private boolean mSingleUidCpuTimesAvailable = true;
@GuardedBy("this")
private boolean mBpfTimesAvailable = true;
// We use the freq count obtained from /proc/uid_time_in_state to decide how many longs
// to read from each /proc/uid/<uid>/time_in_state. On the first read, verify if this is
// correct and if not, set {@link #mSingleUidCpuTimesAvailable} to false. This flag will
@@ -62,6 +64,8 @@ public class KernelSingleUidTimeReader {
private final Injector mInjector;
private static final native boolean canReadBpfTimes();
KernelSingleUidTimeReader(int cpuFreqsCount) {
this(cpuFreqsCount, new Injector());
}
@@ -83,6 +87,18 @@ public class KernelSingleUidTimeReader {
if (!mSingleUidCpuTimesAvailable) {
return null;
}
if (mBpfTimesAvailable) {
final long[] cpuTimesMs = mInjector.readBpfData(uid);
if (cpuTimesMs.length == 0) {
mBpfTimesAvailable = false;
} else if (!mCpuFreqsCountVerified && cpuTimesMs.length != mCpuFreqsCount) {
mSingleUidCpuTimesAvailable = false;
return null;
} else {
mCpuFreqsCountVerified = true;
return computeDelta(uid, cpuTimesMs);
}
}
// Read total cpu times from the proc file.
final String procFile = new StringBuilder(PROC_FILE_DIR)
.append(uid)
@@ -230,6 +246,8 @@ public class KernelSingleUidTimeReader {
public byte[] readData(String procFile) throws IOException {
return Files.readAllBytes(Paths.get(procFile));
}
public native long[] readBpfData(int uid);
}
@VisibleForTesting

View File

@@ -207,6 +207,8 @@ 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_KernelSingleUidTimeReader.cpp",
"com_android_internal_os_Zygote.cpp",
"com_android_internal_os_ZygoteInit.cpp",
"com_android_internal_util_VirtualRefBasePtr.cpp",
@@ -303,6 +305,7 @@ cc_library_shared {
"libdl",
"libdl_android",
"libstatslog",
"libtimeinstate",
"server_configurable_flags",
],

View File

@@ -230,6 +230,8 @@ 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_KernelSingleUidTimeReader(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 +1642,8 @@ 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),
REG_JNI(register_com_android_internal_os_KernelSingleUidTimeReader),
};
/*

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

@@ -0,0 +1,54 @@
/*
* 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 <cputimeinstate.h>
namespace android {
static constexpr uint64_t NSEC_PER_MSEC = 1000000;
static jlongArray copyVecsToArray(JNIEnv *env, std::vector<std::vector<uint64_t>> &vec) {
jsize s = 0;
for (const auto &subVec : vec) s += subVec.size();
jlongArray ar = env->NewLongArray(s);
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();
}
return ar;
}
static jlongArray getUidCpuFreqTimeMs(JNIEnv *env, jclass, jint uid) {
auto out = android::bpf::getUidCpuFreqTimes(uid);
if (!out) return env->NewLongArray(0);
return copyVecsToArray(env, out.value());
}
static const JNINativeMethod g_single_methods[] = {
{"readBpfData", "(I)[J", (void *)getUidCpuFreqTimeMs},
};
int register_com_android_internal_os_KernelSingleUidTimeReader(JNIEnv *env) {
return RegisterMethodsOrDie(env, "com/android/internal/os/KernelSingleUidTimeReader$Injector",
g_single_methods, NELEM(g_single_methods));
}
}

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

View File

@@ -31,20 +31,33 @@ import com.android.internal.os.KernelSingleUidTimeReader.Injector;
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.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.Collection;
@SmallTest
@RunWith(AndroidJUnit4.class)
@RunWith(Parameterized.class)
public class KernelSingleUidTimeReaderTest {
private final static int TEST_UID = 2222;
private final static int TEST_FREQ_COUNT = 5;
private KernelSingleUidTimeReader mReader;
private TestInjector mInjector;
protected boolean mUseBpf;
@Parameters(name="useBpf={0}")
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] { {true}, {false} });
}
public KernelSingleUidTimeReaderTest(boolean useBpf) {
mUseBpf = useBpf;
}
@Before
public void setUp() {
@@ -273,6 +286,7 @@ public class KernelSingleUidTimeReaderTest {
class TestInjector extends Injector {
private byte[] mData;
private long[] mBpfData;
private boolean mThrowExcpetion;
@Override
@@ -284,6 +298,14 @@ public class KernelSingleUidTimeReaderTest {
}
}
@Override
public long[] readBpfData(int uid) {
if (!mUseBpf || mBpfData == null) {
return new long[0];
}
return mBpfData;
}
public void setData(long[] cpuTimes) {
final ByteBuffer buffer = ByteBuffer.allocate(cpuTimes.length * Long.BYTES);
buffer.order(ByteOrder.nativeOrder());
@@ -291,6 +313,7 @@ public class KernelSingleUidTimeReaderTest {
buffer.putLong(time / 10);
}
mData = buffer.array();
mBpfData = cpuTimes.clone();
}
public void letReadDataThrowException(boolean throwException) {