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:
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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
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
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user