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:
202
core/java/com/android/internal/os/KernelCpuUidBpfMapReader.java
Normal file
202
core/java/com/android/internal/os/KernelCpuUidBpfMapReader.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
217
core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp
Normal file
217
core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp
Normal 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
|
||||
@@ -40,6 +40,7 @@ import org.junit.runners.Suite;
|
||||
BatteryStatsUserLifecycleTests.class,
|
||||
KernelCpuProcStringReaderTest.class,
|
||||
KernelCpuUidActiveTimeReaderTest.class,
|
||||
KernelCpuUidBpfMapReaderTest.class,
|
||||
KernelCpuUidClusterTimeReaderTest.class,
|
||||
KernelCpuUidFreqTimeReaderTest.class,
|
||||
KernelCpuUidUserSysTimeReaderTest.class,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user