summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author TreeHugger Robot <treehugger-gerrit@google.com> 2018-11-27 19:28:24 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2018-11-27 19:28:24 +0000
commitfa2b85579e4b75c31e411207b6dca026421d1489 (patch)
treee54be7c8f0abde5fea9b7b15236bc9cbd21cdce5
parent7f2ad6633244b497214b7a5c941dfc1227b3cb54 (diff)
parente45b9b16998aac201fa6462f10a348b0d56ab102 (diff)
Merge "New Kernel Per-UID CPU Time Readers"
-rw-r--r--core/java/com/android/internal/os/KernelCpuProcStringReader.java120
-rw-r--r--core/java/com/android/internal/os/KernelCpuUidTimeReader.java776
-rw-r--r--core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java4
-rw-r--r--core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java27
-rw-r--r--core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java262
-rw-r--r--core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java278
-rw-r--r--core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java359
-rw-r--r--core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java271
8 files changed, 2049 insertions, 48 deletions
diff --git a/core/java/com/android/internal/os/KernelCpuProcStringReader.java b/core/java/com/android/internal/os/KernelCpuProcStringReader.java
index 22435ae7647a..b3aec0c2a561 100644
--- a/core/java/com/android/internal/os/KernelCpuProcStringReader.java
+++ b/core/java/com/android/internal/os/KernelCpuProcStringReader.java
@@ -25,6 +25,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.CharBuffer;
import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
@@ -59,6 +60,7 @@ public class KernelCpuProcStringReader {
private static final String PROC_UID_FREQ_TIME = "/proc/uid_time_in_state";
private static final String PROC_UID_ACTIVE_TIME = "/proc/uid_concurrent_active_time";
private static final String PROC_UID_CLUSTER_TIME = "/proc/uid_concurrent_policy_time";
+ private static final String PROC_UID_USER_SYS_TIME = "/proc/uid_cputime/show_uid_stat";
private static final KernelCpuProcStringReader FREQ_TIME_READER =
new KernelCpuProcStringReader(PROC_UID_FREQ_TIME);
@@ -66,19 +68,25 @@ public class KernelCpuProcStringReader {
new KernelCpuProcStringReader(PROC_UID_ACTIVE_TIME);
private static final KernelCpuProcStringReader CLUSTER_TIME_READER =
new KernelCpuProcStringReader(PROC_UID_CLUSTER_TIME);
+ private static final KernelCpuProcStringReader USER_SYS_TIME_READER =
+ new KernelCpuProcStringReader(PROC_UID_USER_SYS_TIME);
- public static KernelCpuProcStringReader getFreqTimeReaderInstance() {
+ static KernelCpuProcStringReader getFreqTimeReaderInstance() {
return FREQ_TIME_READER;
}
- public static KernelCpuProcStringReader getActiveTimeReaderInstance() {
+ static KernelCpuProcStringReader getActiveTimeReaderInstance() {
return ACTIVE_TIME_READER;
}
- public static KernelCpuProcStringReader getClusterTimeReaderInstance() {
+ static KernelCpuProcStringReader getClusterTimeReaderInstance() {
return CLUSTER_TIME_READER;
}
+ static KernelCpuProcStringReader getUserSysTimeReaderInstance() {
+ return USER_SYS_TIME_READER;
+ }
+
private int mErrors = 0;
private final Path mFile;
private char[] mBuf;
@@ -164,12 +172,12 @@ public class KernelCpuProcStringReader {
// ReentrantReadWriteLock allows lock downgrading.
mReadLock.lock();
return new ProcFileIterator(total);
- } catch (FileNotFoundException e) {
+ } catch (FileNotFoundException | NoSuchFileException e) {
mErrors++;
Slog.w(TAG, "File not found. It's normal if not implemented: " + mFile);
} catch (IOException e) {
mErrors++;
- Slog.e(TAG, "Error reading: " + mFile, e);
+ Slog.e(TAG, "Error reading " + mFile, e);
} finally {
StrictMode.setThreadPolicyMask(oldMask);
mWriteLock.unlock();
@@ -193,6 +201,11 @@ public class KernelCpuProcStringReader {
mSize = size;
}
+ /** @return Whether there are more lines in the iterator. */
+ public boolean hasNextLine() {
+ return mPos < mSize;
+ }
+
/**
* Fetches the next line. Note that all subsequent return values share the same char[]
* under the hood.
@@ -214,44 +227,6 @@ public class KernelCpuProcStringReader {
return CharBuffer.wrap(mBuf, start, i - start);
}
- /**
- * Fetches the next line, converts all numbers into long, and puts into the given long[].
- * To avoid GC, caller should try to use the same array for all calls. All non-numeric
- * chars are treated as delimiters. All numbers are non-negative.
- *
- * @param array An array to store the parsed numbers.
- * @return The number of elements written to the given array. -1 if there is no more line.
- */
- public int nextLineAsArray(long[] array) {
- CharBuffer buf = nextLine();
- if (buf == null) {
- return -1;
- }
- int count = 0;
- long num = -1;
- char c;
-
- while (buf.remaining() > 0 && count < array.length) {
- c = buf.get();
- if (num < 0) {
- if (isNumber(c)) {
- num = c - '0';
- }
- } else {
- if (isNumber(c)) {
- num = num * 10 + c - '0';
- } else {
- array[count++] = num;
- num = -1;
- }
- }
- }
- if (num >= 0) {
- array[count++] = num;
- }
- return count;
- }
-
/** Total size of the proc file in chars. */
public int size() {
return mSize;
@@ -262,8 +237,63 @@ public class KernelCpuProcStringReader {
mReadLock.unlock();
}
- private boolean isNumber(char c) {
- return c >= '0' && c <= '9';
+
+ }
+
+ /**
+ * Converts all numbers in the CharBuffer into longs, and puts into the given long[].
+ *
+ * Space and colon are treated as delimiters. All other chars are not allowed. All numbers
+ * are non-negative. To avoid GC, caller should try to use the same array for all calls.
+ *
+ * This method also resets the given buffer to the original position before return so that
+ * it can be read again.
+ *
+ * @param buf The char buffer to be converted.
+ * @param array An array to store the parsed numbers.
+ * @return The number of elements written to the given array. -1 if buf is null, -2 if buf
+ * contains invalid char, -3 if any number overflows.
+ */
+ public static int asLongs(CharBuffer buf, long[] array) {
+ if (buf == null) {
+ return -1;
+ }
+ final int initialPos = buf.position();
+ int count = 0;
+ long num = -1;
+ char c;
+
+ while (buf.remaining() > 0 && count < array.length) {
+ c = buf.get();
+ if (!(isNumber(c) || c == ' ' || c == ':')) {
+ buf.position(initialPos);
+ return -2;
+ }
+ if (num < 0) {
+ if (isNumber(c)) {
+ num = c - '0';
+ }
+ } else {
+ if (isNumber(c)) {
+ num = num * 10 + c - '0';
+ if (num < 0) {
+ buf.position(initialPos);
+ return -3;
+ }
+ } else {
+ array[count++] = num;
+ num = -1;
+ }
+ }
+ }
+ if (num >= 0) {
+ array[count++] = num;
}
+ buf.position(initialPos);
+ return count;
+ }
+
+ private static boolean isNumber(char c) {
+ return c >= '0' && c <= '9';
}
}
diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
new file mode 100644
index 000000000000..7021b5781347
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
@@ -0,0 +1,776 @@
+/*
+ * Copyright (C) 2018 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 com.android.internal.os.KernelCpuProcStringReader.asLongs;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.StrictMode;
+import android.os.SystemClock;
+import android.util.IntArray;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.KernelCpuProcStringReader.ProcFileIterator;
+
+import java.io.BufferedReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.CharBuffer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * Reads per-UID CPU time proc files. Concrete implementations are all nested inside.
+ *
+ * This class uses a throttler to reject any {@link #readDelta} or {@link #readAbsolute} call
+ * within {@link #mMinTimeBetweenRead}. The throttler can be enable / disabled via a param in
+ * the constructor.
+ *
+ * This class and its subclasses are NOT thread-safe and NOT designed to be accessed by more than
+ * one caller since each caller has its own view of delta.
+ *
+ * @param <T> The type of CPU time for the callback.
+ */
+public abstract class KernelCpuUidTimeReader<T> {
+ protected static final boolean DEBUG = false;
+ private static final long DEFAULT_MIN_TIME_BETWEEN_READ = 1000L; // In milliseconds
+
+ final String mTag = this.getClass().getSimpleName();
+ final SparseArray<T> mLastTimes = new SparseArray<>();
+ final KernelCpuProcStringReader mReader;
+ final boolean mThrottle;
+ private long mMinTimeBetweenRead = DEFAULT_MIN_TIME_BETWEEN_READ;
+ private long mLastReadTimeMs = 0;
+
+ /**
+ * Callback interface for processing each line of the proc file.
+ *
+ * @param <T> The type of CPU time for the callback function.
+ */
+ public interface Callback<T> {
+ /**
+ * @param uid UID of the app
+ * @param time Time spent. The exact data structure depends on subclass implementation.
+ */
+ void onUidCpuTime(int uid, T time);
+ }
+
+ KernelCpuUidTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
+ mReader = reader;
+ mThrottle = throttle;
+ }
+
+ /**
+ * Reads the proc file, calling into the callback with a delta of time for each UID.
+ *
+ * @param cb The callback to invoke for each line of the proc file. If null,the data is
+ * consumed and subsequent calls to readDelta will provide a fresh delta.
+ */
+ public void readDelta(@Nullable Callback<T> cb) {
+ if (!mThrottle) {
+ readDeltaImpl(cb);
+ return;
+ }
+ final long currTimeMs = SystemClock.elapsedRealtime();
+ if (currTimeMs < mLastReadTimeMs + mMinTimeBetweenRead) {
+ if (DEBUG) {
+ Slog.d(mTag, "Throttle readDelta");
+ }
+ return;
+ }
+ readDeltaImpl(cb);
+ mLastReadTimeMs = currTimeMs;
+ }
+
+ /**
+ * Reads the proc file, calling into the callback with cumulative time for each UID.
+ *
+ * @param cb The callback to invoke for each line of the proc file. It cannot be null.
+ */
+ public void readAbsolute(Callback<T> cb) {
+ if (!mThrottle) {
+ readAbsoluteImpl(cb);
+ return;
+ }
+ final long currTimeMs = SystemClock.elapsedRealtime();
+ if (currTimeMs < mLastReadTimeMs + mMinTimeBetweenRead) {
+ if (DEBUG) {
+ Slog.d(mTag, "Throttle readAbsolute");
+ }
+ return;
+ }
+ readAbsoluteImpl(cb);
+ mLastReadTimeMs = currTimeMs;
+ }
+
+ abstract void readDeltaImpl(@Nullable Callback<T> cb);
+
+ abstract void readAbsoluteImpl(Callback<T> callback);
+
+ /**
+ * Removes the UID from internal accounting data. This method, overridden in
+ * {@link KernelCpuUidUserSysTimeReader}, also removes the UID from the kernel module.
+ *
+ * @param uid The UID to remove.
+ * @see KernelCpuUidUserSysTimeReader#removeUid(int)
+ */
+ public void removeUid(int uid) {
+ mLastTimes.delete(uid);
+ }
+
+ /**
+ * Removes UIDs in a given range from internal accounting data. This method, overridden in
+ * {@link KernelCpuUidUserSysTimeReader}, also removes the UIDs from the kernel module.
+ *
+ * @param startUid the first uid to remove.
+ * @param endUid the last uid to remove.
+ * @see KernelCpuUidUserSysTimeReader#removeUidsInRange(int, int)
+ */
+ public void removeUidsInRange(int startUid, int endUid) {
+ if (endUid < startUid) {
+ Slog.e(mTag, "start UID " + startUid + " > end UID " + endUid);
+ return;
+ }
+ mLastTimes.put(startUid, null);
+ mLastTimes.put(endUid, null);
+ final int firstIndex = mLastTimes.indexOfKey(startUid);
+ final int lastIndex = mLastTimes.indexOfKey(endUid);
+ mLastTimes.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
+ }
+
+ /**
+ * Set the minimum time in milliseconds between reads. If throttle is not enabled, this method
+ * has no effect.
+ *
+ * @param minTimeBetweenRead The minimum time in milliseconds.
+ */
+ public void setThrottle(long minTimeBetweenRead) {
+ if (mThrottle && minTimeBetweenRead >= 0) {
+ mMinTimeBetweenRead = minTimeBetweenRead;
+ }
+ }
+
+ /**
+ * Reads /proc/uid_cputime/show_uid_stat which has the line format:
+ *
+ * uid: user_time_micro_seconds system_time_micro_seconds power_in_milli-amp-micro_seconds
+ *
+ * This provides the time a UID's processes spent executing in user-space and kernel-space.
+ * The file contains a monotonically increasing count of time for a single boot. This class
+ * maintains the previous results of a call to {@link #readDelta} in order to provide a proper
+ * delta.
+ */
+ public static class KernelCpuUidUserSysTimeReader extends KernelCpuUidTimeReader<long[]> {
+ private static final String REMOVE_UID_PROC_FILE = "/proc/uid_cputime/remove_uid_range";
+
+ // [uid, user_time, system_time, (maybe) power_in_milli-amp-micro_seconds]
+ private final long[] mBuffer = new long[4];
+ // A reusable array to hold [user_time, system_time] for the callback.
+ private final long[] mUsrSysTime = new long[2];
+
+ public KernelCpuUidUserSysTimeReader(boolean throttle) {
+ super(KernelCpuProcStringReader.getUserSysTimeReaderInstance(), throttle);
+ }
+
+ @VisibleForTesting
+ public KernelCpuUidUserSysTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
+ super(reader, throttle);
+ }
+
+ @Override
+ void readDeltaImpl(@Nullable Callback<long[]> cb) {
+ try (ProcFileIterator iter = mReader.open(!mThrottle)) {
+ if (iter == null) {
+ return;
+ }
+ CharBuffer buf;
+ while ((buf = iter.nextLine()) != null) {
+ if (asLongs(buf, mBuffer) < 3) {
+ 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[2];
+ mLastTimes.put(uid, lastTimes);
+ }
+ final long currUsrTimeUs = mBuffer[1];
+ final long currSysTimeUs = mBuffer[2];
+ mUsrSysTime[0] = currUsrTimeUs - lastTimes[0];
+ mUsrSysTime[1] = currSysTimeUs - lastTimes[1];
+
+ if (mUsrSysTime[0] < 0 || mUsrSysTime[1] < 0) {
+ Slog.e(mTag, "Negative user/sys time delta for UID=" + uid
+ + "\nPrev times: u=" + lastTimes[0] + " s=" + lastTimes[1]
+ + " Curr times: u=" + currUsrTimeUs + " s=" + currSysTimeUs);
+ } else if (mUsrSysTime[0] > 0 || mUsrSysTime[1] > 0) {
+ if (cb != null) {
+ cb.onUidCpuTime(uid, mUsrSysTime);
+ }
+ }
+ lastTimes[0] = currUsrTimeUs;
+ lastTimes[1] = currSysTimeUs;
+ }
+ }
+ }
+
+ @Override
+ void readAbsoluteImpl(Callback<long[]> cb) {
+ try (ProcFileIterator iter = mReader.open(!mThrottle)) {
+ if (iter == null) {
+ return;
+ }
+ CharBuffer buf;
+ while ((buf = iter.nextLine()) != null) {
+ if (asLongs(buf, mBuffer) < 3) {
+ Slog.wtf(mTag, "Invalid line: " + buf.toString());
+ continue;
+ }
+ mUsrSysTime[0] = mBuffer[1]; // User time in microseconds
+ mUsrSysTime[1] = mBuffer[2]; // System time in microseconds
+ cb.onUidCpuTime((int) mBuffer[0], mUsrSysTime);
+ }
+ }
+ }
+
+ @Override
+ public void removeUid(int uid) {
+ super.removeUid(uid);
+ removeUidsFromKernelModule(uid, uid);
+ }
+
+ @Override
+ public void removeUidsInRange(int startUid, int endUid) {
+ super.removeUidsInRange(startUid, endUid);
+ removeUidsFromKernelModule(startUid, endUid);
+ }
+
+ /**
+ * Removes UIDs in a given range from the kernel module and internal accounting data. Only
+ * {@link BatteryStatsImpl} and its child processes should call this, as the change on
+ * Kernel is
+ * visible system wide.
+ *
+ * @param startUid the first uid to remove
+ * @param endUid the last uid to remove
+ */
+ private void removeUidsFromKernelModule(int startUid, int endUid) {
+ Slog.d(mTag, "Removing uids " + startUid + "-" + endUid);
+ final int oldMask = StrictMode.allowThreadDiskWritesMask();
+ try (FileWriter writer = new FileWriter(REMOVE_UID_PROC_FILE)) {
+ writer.write(startUid + "-" + endUid);
+ writer.flush();
+ } catch (IOException e) {
+ Slog.e(mTag, "failed to remove uids " + startUid + " - " + endUid
+ + " from uid_cputime module", e);
+ } finally {
+ StrictMode.setThreadPolicyMask(oldMask);
+ }
+ }
+ }
+
+ /**
+ * Reads /proc/uid_time_in_state which has the format:
+ *
+ * uid: [freq1] [freq2] [freq3] ...
+ * [uid1]: [time in freq1] [time in freq2] [time in freq3] ...
+ * [uid2]: [time in freq1] [time in freq2] [time in freq3] ...
+ * ...
+ *
+ * This provides the times a UID's processes spent executing at each different cpu frequency.
+ * The file contains a monotonically increasing count of time for a single boot. This class
+ * maintains the previous results of a call to {@link #readDelta} in order to provide a proper
+ * delta.
+ */
+ public static class KernelCpuUidFreqTimeReader extends KernelCpuUidTimeReader<long[]> {
+ private static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
+ // We check the existence of proc file a few times (just in case it is not ready yet when we
+ // start reading) and if it is not available, we simply ignore further read requests.
+ private static final int MAX_ERROR_COUNT = 5;
+
+ private final Path mProcFilePath;
+ private long[] mBuffer;
+ private long[] mCurTimes;
+ private long[] mDeltaTimes;
+ private long[] mCpuFreqs;
+
+ private int mFreqCount = 0;
+ private int mErrors = 0;
+ private boolean mPerClusterTimesAvailable;
+ private boolean mAllUidTimesAvailable = true;
+
+ public KernelCpuUidFreqTimeReader(boolean throttle) {
+ this(UID_TIMES_PROC_FILE, KernelCpuProcStringReader.getFreqTimeReaderInstance(),
+ throttle);
+ }
+
+ @VisibleForTesting
+ public KernelCpuUidFreqTimeReader(String procFile, KernelCpuProcStringReader reader,
+ boolean throttle) {
+ super(reader, throttle);
+ mProcFilePath = Paths.get(procFile);
+ }
+
+ /**
+ * @return Whether per-cluster times are available.
+ */
+ public boolean perClusterTimesAvailable() {
+ return mPerClusterTimesAvailable;
+ }
+
+ /**
+ * @return Whether all-UID times are available.
+ */
+ public boolean allUidTimesAvailable() {
+ return mAllUidTimesAvailable;
+ }
+
+ /**
+ * @return A map of all UIDs to their CPU time-in-state array in milliseconds.
+ */
+ public SparseArray<long[]> getAllUidCpuFreqTimeMs() {
+ return mLastTimes;
+ }
+
+ /**
+ * Reads a list of CPU frequencies from /proc/uid_time_in_state. Uses a given PowerProfile
+ * to determine if per-cluster times are available.
+ *
+ * @param powerProfile The PowerProfile to compare against.
+ * @return A long[] of CPU frequencies in Hz.
+ */
+ public long[] readFreqs(@NonNull PowerProfile powerProfile) {
+ checkNotNull(powerProfile);
+ if (mCpuFreqs != null) {
+ // No need to read cpu freqs more than once.
+ return mCpuFreqs;
+ }
+ if (!mAllUidTimesAvailable) {
+ return 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);
+ }
+ // Check if the freqs in the proc file correspond to per-cluster freqs.
+ final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs();
+ final int numClusters = powerProfile.getNumCpuClusters();
+ if (numClusterFreqs.size() == numClusters) {
+ mPerClusterTimesAvailable = true;
+ for (int i = 0; i < numClusters; ++i) {
+ if (numClusterFreqs.get(i) != powerProfile.getNumSpeedStepsInCpuCluster(i)) {
+ mPerClusterTimesAvailable = false;
+ break;
+ }
+ }
+ } else {
+ mPerClusterTimesAvailable = false;
+ }
+ Slog.i(mTag, "mPerClusterTimesAvailable=" + mPerClusterTimesAvailable);
+ return mCpuFreqs;
+ }
+
+ private long[] readFreqs(String line) {
+ if (line == null) {
+ return null;
+ }
+ final String[] lineArray = line.split(" ");
+ if (lineArray.length <= 1) {
+ Slog.wtf(mTag, "Malformed freq line: " + line);
+ return null;
+ }
+ mFreqCount = lineArray.length - 1;
+ mCpuFreqs = new long[mFreqCount];
+ mCurTimes = new long[mFreqCount];
+ mDeltaTimes = new long[mFreqCount];
+ mBuffer = new long[mFreqCount + 1];
+ for (int i = 0; i < mFreqCount; ++i) {
+ mCpuFreqs[i] = Long.parseLong(lineArray[i + 1], 10);
+ }
+ return mCpuFreqs;
+ }
+
+ @Override
+ void readDeltaImpl(@Nullable Callback<long[]> cb) {
+ try (ProcFileIterator iter = mReader.open(!mThrottle)) {
+ if (!checkPrecondition(iter)) {
+ return;
+ }
+ CharBuffer buf;
+ while ((buf = iter.nextLine()) != null) {
+ if (asLongs(buf, mBuffer) != mBuffer.length) {
+ 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);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ void readAbsoluteImpl(Callback<long[]> cb) {
+ try (ProcFileIterator iter = mReader.open(!mThrottle)) {
+ if (!checkPrecondition(iter)) {
+ return;
+ }
+ CharBuffer buf;
+ while ((buf = iter.nextLine()) != null) {
+ if (asLongs(buf, mBuffer) != mBuffer.length) {
+ Slog.wtf(mTag, "Invalid line: " + buf.toString());
+ continue;
+ }
+ copyToCurTimes();
+ cb.onUidCpuTime((int) mBuffer[0], mCurTimes);
+ }
+ }
+ }
+
+ private void copyToCurTimes() {
+ for (int i = 0; i < mFreqCount; i++) {
+ mCurTimes[i] = mBuffer[i + 1] * 10;
+ }
+ }
+
+ private boolean checkPrecondition(ProcFileIterator iter) {
+ if (iter == null || !iter.hasNextLine()) {
+ // Error logged in KernelCpuProcStringReader.
+ return false;
+ }
+ CharBuffer line = iter.nextLine();
+ if (mCpuFreqs != null) {
+ return true;
+ }
+ return readFreqs(line.toString()) != null;
+ }
+
+ /**
+ * Extracts no. of cpu clusters and no. of freqs in each of these clusters from the freqs
+ * read from the proc file.
+ *
+ * We need to assume that freqs in each cluster are strictly increasing.
+ * For e.g. if the freqs read from proc file are: 12, 34, 15, 45, 12, 15, 52. Then it means
+ * there are 3 clusters: (12, 34), (15, 45), (12, 15, 52)
+ *
+ * @return an IntArray filled with no. of freqs in each cluster.
+ */
+ private IntArray extractClusterInfoFromProcFileFreqs() {
+ final IntArray numClusterFreqs = new IntArray();
+ int freqsFound = 0;
+ for (int i = 0; i < mFreqCount; ++i) {
+ freqsFound++;
+ if (i + 1 == mFreqCount || mCpuFreqs[i + 1] <= mCpuFreqs[i]) {
+ numClusterFreqs.add(freqsFound);
+ freqsFound = 0;
+ }
+ }
+ return numClusterFreqs;
+ }
+ }
+
+ /**
+ * Reads /proc/uid_concurrent_active_time and reports CPU active time to BatteryStats to
+ * compute {@link PowerProfile#POWER_CPU_ACTIVE}.
+ *
+ * /proc/uid_concurrent_active_time has the following format:
+ * cpus: n
+ * uid0: time0a, time0b, ..., time0n,
+ * uid1: time1a, time1b, ..., time1n,
+ * uid2: time2a, time2b, ..., time2n,
+ * ...
+ * where n is the total number of cpus (num_possible_cpus)
+ * timeXn means the CPU time that a UID X spent running concurrently with n other processes.
+ *
+ * The file contains a monotonically increasing count of time for a single boot. This class
+ * maintains the previous results of a call to {@link #readDelta} in order to provide a
+ * proper delta.
+ */
+ public static class KernelCpuUidActiveTimeReader extends KernelCpuUidTimeReader<Long> {
+ private int mCores = 0;
+ private long[] mBuffer;
+
+ public KernelCpuUidActiveTimeReader(boolean throttle) {
+ super(KernelCpuProcStringReader.getActiveTimeReaderInstance(), throttle);
+ }
+
+ @VisibleForTesting
+ public KernelCpuUidActiveTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
+ super(reader, throttle);
+ }
+
+ @Override
+ void readDeltaImpl(@Nullable Callback<Long> cb) {
+ try (ProcFileIterator iter = mReader.open(!mThrottle)) {
+ if (!checkPrecondition(iter)) {
+ return;
+ }
+ CharBuffer buf;
+ while ((buf = iter.nextLine()) != null) {
+ if (asLongs(buf, mBuffer) != mBuffer.length) {
+ 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);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ void readAbsoluteImpl(Callback<Long> cb) {
+ try (ProcFileIterator iter = mReader.open(!mThrottle)) {
+ if (!checkPrecondition(iter)) {
+ return;
+ }
+ CharBuffer buf;
+ while ((buf = iter.nextLine()) != null) {
+ if (asLongs(buf, mBuffer) != mBuffer.length) {
+ Slog.wtf(mTag, "Invalid line: " + buf.toString());
+ continue;
+ }
+ long cpuActiveTime = sumActiveTime(mBuffer);
+ if (cpuActiveTime > 0) {
+ cb.onUidCpuTime((int) mBuffer[0], cpuActiveTime);
+ }
+ }
+ }
+ }
+
+ private static long sumActiveTime(long[] times) {
+ // 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.
+ }
+ return (long) sum;
+ }
+
+ private boolean checkPrecondition(ProcFileIterator iter) {
+ if (iter == null || !iter.hasNextLine()) {
+ // Error logged in KernelCpuProcStringReader.
+ return false;
+ }
+ CharBuffer line = iter.nextLine();
+ if (mCores > 0) {
+ return true;
+ }
+
+ String str = line.toString();
+ if (!str.startsWith("cpus:")) {
+ Slog.wtf(mTag, "Malformed uid_concurrent_active_time line: " + line);
+ return false;
+ }
+ int cores = Integer.parseInt(str.substring(5).trim(), 10);
+ if (cores <= 0) {
+ Slog.wtf(mTag, "Malformed uid_concurrent_active_time line: " + line);
+ return false;
+ }
+ mCores = cores;
+ mBuffer = new long[mCores + 1]; // UID is stored at mBuffer[0].
+ return true;
+ }
+ }
+
+
+ /**
+ * Reads /proc/uid_concurrent_policy_time and reports CPU cluster times to BatteryStats to
+ * compute cluster power. See {@link PowerProfile#getAveragePowerForCpuCluster(int)}.
+ *
+ * /proc/uid_concurrent_policy_time has the following format:
+ * policyX: x policyY: y policyZ: z...
+ * uid1, time1a, time1b, ..., time1n,
+ * uid2, time2a, time2b, ..., time2n,
+ * ...
+ * The first line lists all policies (i.e. clusters) followed by # cores in each policy.
+ * Each uid is followed by x time entries corresponding to the time it spent on clusterX
+ * running concurrently with 0, 1, 2, ..., x - 1 other processes, then followed by y, z, ...
+ * time entries.
+ *
+ * The file contains a monotonically increasing count of time for a single boot. This class
+ * maintains the previous results of a call to {@link #readDelta} in order to provide a
+ * proper delta.
+ */
+ public static class KernelCpuUidClusterTimeReader extends KernelCpuUidTimeReader<long[]> {
+ private int mNumClusters;
+ private int mNumCores;
+ private int[] mCoresOnClusters; // # cores on each cluster.
+ private long[] mBuffer; // To store data returned from ProcFileIterator.
+ private long[] mCurTime;
+ private long[] mDeltaTime;
+
+ public KernelCpuUidClusterTimeReader(boolean throttle) {
+ super(KernelCpuProcStringReader.getClusterTimeReaderInstance(), throttle);
+ }
+
+ @VisibleForTesting
+ public KernelCpuUidClusterTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
+ super(reader, throttle);
+ }
+
+ @Override
+ void readDeltaImpl(@Nullable Callback<long[]> cb) {
+ try (ProcFileIterator iter = mReader.open(!mThrottle)) {
+ if (!checkPrecondition(iter)) {
+ return;
+ }
+ CharBuffer buf;
+ while ((buf = iter.nextLine()) != null) {
+ if (asLongs(buf, mBuffer) != mBuffer.length) {
+ 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);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ void readAbsoluteImpl(Callback<long[]> cb) {
+ try (ProcFileIterator iter = mReader.open(!mThrottle)) {
+ if (!checkPrecondition(iter)) {
+ return;
+ }
+ CharBuffer buf;
+ while ((buf = iter.nextLine()) != null) {
+ if (asLongs(buf, mBuffer) != mBuffer.length) {
+ Slog.wtf(mTag, "Invalid line: " + buf.toString());
+ continue;
+ }
+ sumClusterTime();
+ cb.onUidCpuTime((int) mBuffer[0], mCurTime);
+ }
+ }
+ }
+
+ private void sumClusterTime() {
+ // 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.
+ }
+ mCurTime[i] = (long) sum;
+ }
+ }
+
+ private boolean checkPrecondition(ProcFileIterator iter) {
+ if (iter == null || !iter.hasNextLine()) {
+ // Error logged in KernelCpuProcStringReader.
+ return false;
+ }
+ CharBuffer line = iter.nextLine();
+ if (mNumClusters > 0) {
+ return true;
+ }
+ // Parse # cores in clusters.
+ String[] lineArray = line.toString().split(" ");
+ if (lineArray.length % 2 != 0) {
+ Slog.wtf(mTag, "Malformed uid_concurrent_policy_time line: " + line);
+ return false;
+ }
+ int[] clusters = new int[lineArray.length / 2];
+ int cores = 0;
+ for (int i = 0; i < clusters.length; i++) {
+ if (!lineArray[i * 2].startsWith("policy")) {
+ Slog.wtf(mTag, "Malformed uid_concurrent_policy_time line: " + line);
+ return false;
+ }
+ clusters[i] = Integer.parseInt(lineArray[i * 2 + 1], 10);
+ cores += clusters[i];
+ }
+ mNumClusters = clusters.length;
+ mNumCores = cores;
+ mCoresOnClusters = clusters;
+ mBuffer = new long[cores + 1];
+ mCurTime = new long[mNumClusters];
+ mDeltaTime = new long[mNumClusters];
+ return true;
+ }
+ }
+
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 3cfc6443b15e..225515e9e3f3 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -39,6 +39,10 @@ import org.junit.runners.Suite;
BatteryStatsUserLifecycleTests.class,
KernelCpuProcReaderTest.class,
KernelCpuProcStringReaderTest.class,
+ KernelCpuUidActiveTimeReaderTest.class,
+ KernelCpuUidClusterTimeReaderTest.class,
+ KernelCpuUidFreqTimeReaderTest.class,
+ KernelCpuUidUserSysTimeReaderTest.class,
KernelMemoryBandwidthStatsTest.class,
KernelSingleUidTimeReaderTest.class,
KernelUidCpuFreqTimeReaderTest.class,
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java
index dae9eb57e237..2663f2bc8ae1 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java
@@ -37,6 +37,7 @@ import java.io.BufferedWriter;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.nio.CharBuffer;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
@@ -149,7 +150,7 @@ public class KernelCpuProcStringReaderTest {
+ "0 0 1 1 1 0 2 0 221",
iter.nextLine().toString());
long[] actual = new long[43];
- iter.nextLineAsArray(actual);
+ KernelCpuProcStringReader.asLongs(iter.nextLine(), actual);
assertArrayEquals(
new long[]{50227, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 196, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 721},
@@ -183,7 +184,7 @@ public class KernelCpuProcStringReaderTest {
}
}
- /** Tests nextLineToArray functionality. */
+ /** Tests reading lines, then converting to long[]. */
@Test
public void testReadLineToArray() throws Exception {
final long[][] data = getTestArray(800, 50);
@@ -193,12 +194,32 @@ public class KernelCpuProcStringReaderTest {
long[] actual = new long[50];
try (KernelCpuProcStringReader.ProcFileIterator iter = mReader.open()) {
for (long[] expected : data) {
- assertEquals(50, iter.nextLineAsArray(actual));
+ CharBuffer cb = iter.nextLine();
+ String before = cb.toString();
+ assertEquals(50, KernelCpuProcStringReader.asLongs(cb, actual));
assertArrayEquals(expected, actual);
+ assertEquals("Buffer not reset to the pos before reading", before, cb.toString());
}
}
}
+ /** Tests error handling of converting to long[]. */
+ @Test
+ public void testLineToArrayErrorHandling() {
+ long[] actual = new long[100];
+ String invalidChar = "123: -1234 456";
+ String overflow = "123: 999999999999999999999999999999999999999999999999999999999 123";
+ CharBuffer cb = CharBuffer.wrap("----" + invalidChar + "+++", 4, 4 + invalidChar.length());
+ assertEquals("Failed to report err for: " + invalidChar, -2,
+ KernelCpuProcStringReader.asLongs(cb, actual));
+ assertEquals("Buffer not reset to the same pos before reading", invalidChar, cb.toString());
+
+ cb = CharBuffer.wrap("----" + overflow + "+++", 4, 4 + overflow.length());
+ assertEquals("Failed to report err for: " + overflow, -3,
+ KernelCpuProcStringReader.asLongs(cb, actual));
+ assertEquals("Buffer not reset to the pos before reading", overflow, cb.toString());
+ }
+
/**
* Tests that reading a file over the limit (1MB) will return null.
*/
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java
new file mode 100644
index 000000000000..adafda04d516
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2018 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.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.os.FileUtils;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseLongArray;
+
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.Random;
+
+/**
+ * Test class for {@link KernelCpuUidActiveTimeReader}.
+ *
+ * $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuUidActiveTimeReaderTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KernelCpuUidActiveTimeReaderTest {
+ private File mTestDir;
+ private File mTestFile;
+ private KernelCpuUidActiveTimeReader mReader;
+ private VerifiableCallback mCallback;
+
+ private Random mRand = new Random(12345);
+ private final int mCpus = 4;
+ private final String mHeadline = "cpus: 4\n";
+ private final int[] mUids = {0, 1, 22, 333, 4444, 55555};
+
+ private Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+
+ @Before
+ public void setUp() {
+ mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
+ mTestFile = new File(mTestDir, "test.file");
+ mReader = new KernelCpuUidActiveTimeReader(
+ new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+ mCallback = new VerifiableCallback();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ FileUtils.deleteContents(mTestDir);
+ FileUtils.deleteContents(getContext().getFilesDir());
+ }
+
+ @Test
+ public void testReadDelta() throws Exception {
+ final long[][] times = increaseTime(new long[mUids.length][mCpus]);
+ writeToFile(mHeadline + uidLines(mUids, times));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < mUids.length; ++i) {
+ mCallback.verify(mUids[i], getActiveTime(times[i]));
+ }
+ mCallback.verifyNoMoreInteractions();
+
+ // Verify that a second call will only return deltas.
+ mCallback.clear();
+ final long[][] newTimes1 = increaseTime(times);
+ writeToFile(mHeadline + uidLines(mUids, newTimes1));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < mUids.length; ++i) {
+ mCallback.verify(mUids[i], getActiveTime(newTimes1[i]) - getActiveTime(times[i]));
+ }
+ mCallback.verifyNoMoreInteractions();
+
+ // Verify that there won't be a callback if the proc file values didn't change.
+ mCallback.clear();
+ mReader.readDelta(mCallback);
+ mCallback.verifyNoMoreInteractions();
+
+ // 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));
+ mReader.readDelta(null);
+ mCallback.verifyNoMoreInteractions();
+
+ // Verify that the readDelta call will only return deltas when
+ // the previous call had null callback.
+ mCallback.clear();
+ final long[][] newTimes3 = increaseTime(newTimes2);
+ 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());
+ }
+
+ @Test
+ public void testReadAbsolute() throws Exception {
+ final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
+ writeToFile(mHeadline + uidLines(mUids, times1));
+ mReader.readAbsolute(mCallback);
+ for (int i = 0; i < mUids.length; i++) {
+ mCallback.verify(mUids[i], getActiveTime(times1[i]));
+ }
+ mCallback.verifyNoMoreInteractions();
+
+ // Verify that a second call should still return absolute values
+ mCallback.clear();
+ final long[][] times2 = increaseTime(times1);
+ writeToFile(mHeadline + uidLines(mUids, times2));
+ mReader.readAbsolute(mCallback);
+ for (int i = 0; i < mUids.length; i++) {
+ mCallback.verify(mUids[i], getActiveTime(times2[i]));
+ }
+ mCallback.verifyNoMoreInteractions();
+ assertTrue(mTestFile.delete());
+ }
+
+ @Test
+ public void testReadDeltaDecreasedTime() throws Exception {
+ final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
+ writeToFile(mHeadline + uidLines(mUids, 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);
+ System.arraycopy(times1[0], 0, times2[0], 0, mCpus);
+ times2[0][0] = 100;
+ writeToFile(mHeadline + uidLines(mUids, 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());
+
+ // 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));
+ mReader.readDelta(mCallback);
+ mCallback.verify(mUids[0], getActiveTime(times3[0]) - getActiveTime(times1[0]));
+ for (int i = 1; i < mUids.length; i++) {
+ mCallback.verify(mUids[i], getActiveTime(times3[i]) - getActiveTime(times2[i]));
+ }
+ mCallback.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void testReadDeltaNegativeTime() throws Exception {
+ final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
+ writeToFile(mHeadline + uidLines(mUids, 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));
+ 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());
+
+ // 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));
+ mReader.readDelta(mCallback);
+ mCallback.verify(mUids[0], getActiveTime(times3[0]) - getActiveTime(times1[0]));
+ for (int i = 1; i < mUids.length; i++) {
+ mCallback.verify(mUids[i], getActiveTime(times3[i]) - getActiveTime(times2[i]));
+ }
+ mCallback.verifyNoMoreInteractions();
+ }
+
+ private String uidLines(int[] uids, long[][] times) {
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < uids.length; i++) {
+ sb.append(uids[i]).append(':');
+ for (int j = 0; j < times[i].length; j++) {
+ sb.append(' ').append(times[i][j] / 10);
+ }
+ sb.append('\n');
+ }
+ return sb.toString();
+ }
+
+ private void writeToFile(String s) throws IOException {
+ try (BufferedWriter w = Files.newBufferedWriter(mTestFile.toPath())) {
+ w.write(s);
+ w.flush();
+ }
+ }
+
+ private long[][] increaseTime(long[][] original) {
+ long[][] newTime = new long[original.length][original[0].length];
+ for (int i = 0; i < original.length; i++) {
+ for (int j = 0; j < original[0].length; j++) {
+ newTime[i][j] = original[i][j] + mRand.nextInt(10000) * 1000 + 1000;
+ }
+ }
+ return newTime;
+ }
+
+ private long getActiveTime(long[] times) {
+ return times[0] + times[1] / 2 + times[2] / 3 + times[3] / 4;
+ }
+
+ private class VerifiableCallback implements KernelCpuUidTimeReader.Callback<Long> {
+ SparseLongArray mData = new SparseLongArray();
+
+ public void verify(int uid, long time) {
+ assertEquals(time, mData.get(uid));
+ mData.delete(uid);
+ }
+
+ public void clear() {
+ mData.clear();
+ }
+
+ @Override
+ public void onUidCpuTime(int uid, Long time) {
+ mData.put(uid, time);
+ }
+
+ public void verifyNoMoreInteractions() {
+ assertEquals(0, mData.size());
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java
new file mode 100644
index 000000000000..ad20d84bfc60
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2018 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.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.os.FileUtils;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
+
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.Random;
+
+/**
+ * Test class for {@link KernelCpuUidClusterTimeReader}.
+ *
+ * $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuUidClusterTimeReaderTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KernelCpuUidClusterTimeReaderTest {
+ private File mTestDir;
+ private File mTestFile;
+ private KernelCpuUidClusterTimeReader mReader;
+ private VerifiableCallback mCallback;
+
+ private Random mRand = new Random(12345);
+ private final int mCpus = 6;
+ private final String mHeadline = "policy0: 4 policy4: 2\n";
+ private final int[] mUids = {0, 1, 22, 333, 4444, 55555};
+
+ private Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+
+ @Before
+ public void setUp() {
+ mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
+ mTestFile = new File(mTestDir, "test.file");
+ mReader = new KernelCpuUidClusterTimeReader(
+ new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+ mCallback = new VerifiableCallback();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ FileUtils.deleteContents(mTestDir);
+ FileUtils.deleteContents(getContext().getFilesDir());
+ }
+
+ @Test
+ public void testReadDelta() throws Exception {
+ final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
+ writeToFile(mHeadline + uidLines(mUids, times1));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < mUids.length; ++i) {
+ mCallback.verify(mUids[i], clusterTime(times1[i]));
+ }
+ mCallback.verifyNoMoreInteractions();
+
+ // Verify that a second call will only return deltas.
+ mCallback.clear();
+ final long[][] times2 = increaseTime(times1);
+ writeToFile(mHeadline + uidLines(mUids, times2));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < mUids.length; ++i) {
+ mCallback.verify(mUids[i], subtract(clusterTime(times2[i]), clusterTime(times1[i])));
+ }
+ mCallback.verifyNoMoreInteractions();
+
+ // Verify that there won't be a callback if the proc file values didn't change.
+ mCallback.clear();
+ mReader.readDelta(mCallback);
+ mCallback.verifyNoMoreInteractions();
+
+ // 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));
+ mReader.readDelta(null);
+ mCallback.verifyNoMoreInteractions();
+
+ // Verify that the readDelta call will only return deltas when
+ // the previous call had null callback.
+ mCallback.clear();
+ final long[][] times4 = increaseTime(times3);
+ writeToFile(mHeadline + uidLines(mUids, 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());
+ }
+
+ @Test
+ public void testReadAbsolute() throws Exception {
+ final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
+ writeToFile(mHeadline + uidLines(mUids, times1));
+ mReader.readAbsolute(mCallback);
+ for (int i = 0; i < mUids.length; i++) {
+ mCallback.verify(mUids[i], clusterTime(times1[i]));
+ }
+ mCallback.verifyNoMoreInteractions();
+
+ // Verify that a second call should still return absolute values
+ mCallback.clear();
+ final long[][] times2 = increaseTime(times1);
+ writeToFile(mHeadline + uidLines(mUids, times2));
+ mReader.readAbsolute(mCallback);
+ for (int i = 0; i < mUids.length; i++) {
+ mCallback.verify(mUids[i], clusterTime(times2[i]));
+ }
+ mCallback.verifyNoMoreInteractions();
+ assertTrue(mTestFile.delete());
+ }
+
+ @Test
+ public void testReadDeltaDecreasedTime() throws Exception {
+ final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
+ writeToFile(mHeadline + uidLines(mUids, 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);
+ System.arraycopy(times1[0], 0, times2[0], 0, mCpus);
+ times2[0][0] = 100;
+ writeToFile(mHeadline + uidLines(mUids, 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());
+
+ // 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));
+ mReader.readDelta(mCallback);
+ mCallback.verify(mUids[0], subtract(clusterTime(times3[0]), clusterTime(times1[0])));
+ for (int i = 1; i < mUids.length; i++) {
+ mCallback.verify(mUids[i], subtract(clusterTime(times3[i]), clusterTime(times2[i])));
+ }
+ mCallback.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void testReadDeltaNegativeTime() throws Exception {
+ final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
+ writeToFile(mHeadline + uidLines(mUids, 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));
+ 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());
+
+ // 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));
+ mReader.readDelta(mCallback);
+ mCallback.verify(mUids[0], subtract(clusterTime(times3[0]), clusterTime(times1[0])));
+ for (int i = 1; i < mUids.length; i++) {
+ mCallback.verify(mUids[i], subtract(clusterTime(times3[i]), clusterTime(times2[i])));
+ }
+ mCallback.verifyNoMoreInteractions();
+ }
+
+ private long[] clusterTime(long[] times) {
+ // Assumes 4 + 2 cores
+ return new long[]{times[0] + times[1] / 2 + times[2] / 3 + times[3] / 4,
+ times[4] + times[5] / 2};
+ }
+
+ private String uidLines(int[] uids, long[][] times) {
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < uids.length; i++) {
+ sb.append(uids[i]).append(':');
+ for (int j = 0; j < times[i].length; j++) {
+ sb.append(' ').append(times[i][j] / 10);
+ }
+ sb.append('\n');
+ }
+ return sb.toString();
+ }
+
+ private void writeToFile(String s) throws IOException {
+ try (BufferedWriter w = Files.newBufferedWriter(mTestFile.toPath())) {
+ w.write(s);
+ w.flush();
+ }
+ }
+
+ private long[][] increaseTime(long[][] original) {
+ long[][] newTime = new long[original.length][original[0].length];
+ for (int i = 0; i < original.length; i++) {
+ for (int j = 0; j < original[0].length; j++) {
+ newTime[i][j] = original[i][j] + mRand.nextInt(10000) * 1000 + 1000;
+ }
+ }
+ return newTime;
+ }
+
+ private long[] subtract(long[] a1, long[] a2) {
+ long[] val = new long[a1.length];
+ for (int i = 0; i < val.length; ++i) {
+ val[i] = a1[i] - a2[i];
+ }
+ return val;
+ }
+
+ private class VerifiableCallback implements KernelCpuUidTimeReader.Callback<long[]> {
+ SparseArray<long[]> mData = new SparseArray<>();
+
+ public void verify(int uid, long[] cpuTimes) {
+ long[] array = mData.get(uid);
+ assertNotNull(array);
+ assertArrayEquals(cpuTimes, array);
+ mData.remove(uid);
+ }
+
+ public void clear() {
+ mData.clear();
+ }
+
+ @Override
+ public void onUidCpuTime(int uid, long[] times) {
+ long[] array = new long[times.length];
+ System.arraycopy(times, 0, array, 0, array.length);
+ mData.put(uid, array);
+ }
+
+ public void verifyNoMoreInteractions() {
+ assertEquals(0, mData.size());
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java
new file mode 100644
index 000000000000..1d3a98a89d95
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2018 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.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.FileUtils;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
+
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.Random;
+
+/**
+ * Test class for {@link KernelCpuUidFreqTimeReader}.
+ *
+ * $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuUidFreqTimeReaderTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KernelCpuUidFreqTimeReaderTest {
+ private File mTestDir;
+ private File mTestFile;
+ private KernelCpuUidFreqTimeReader mReader;
+ private VerifiableCallback mCallback;
+ @Mock
+ private PowerProfile mPowerProfile;
+
+ private Random mRand = new Random(12345);
+ private final int[] mUids = {0, 1, 22, 333, 4444, 55555};
+
+ private Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
+ mTestFile = new File(mTestDir, "test.file");
+ mReader = new KernelCpuUidFreqTimeReader(mTestFile.getAbsolutePath(),
+ new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+ mCallback = new VerifiableCallback();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ FileUtils.deleteContents(mTestDir);
+ FileUtils.deleteContents(getContext().getFilesDir());
+ }
+
+ @Test
+ public void testReadFreqs_perClusterTimesNotAvailable() throws Exception {
+ final long[][] freqs = {
+ {1, 12, 123, 1234},
+ {1, 12, 123, 23, 123, 1234, 12345, 123456},
+ {1, 12, 123, 23, 123, 1234, 12345, 123456, 12, 123, 12345},
+ {1, 12, 123, 23, 2345, 234567}
+ };
+ final int[] numClusters = {2, 2, 3, 1};
+ 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);
+ setCpuClusterFreqs(numClusters[i], numFreqs[i]);
+ writeToFile(freqsLine(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());
+ actualFreqs = mReader.readFreqs(mPowerProfile);
+ assertArrayEquals(freqs[i], actualFreqs);
+ assertFalse(errMsg, mReader.perClusterTimesAvailable());
+ }
+ }
+
+ @Test
+ public void testReadFreqs_perClusterTimesAvailable() throws Exception {
+ final long[][] freqs = {
+ {1, 12, 123, 1234},
+ {1, 12, 123, 23, 123, 1234, 12345, 123456},
+ {1, 12, 123, 23, 123, 1234, 12345, 123456, 12, 123, 12345, 1234567}
+ };
+ final int[] numClusters = {1, 2, 3};
+ 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);
+ setCpuClusterFreqs(numClusters[i], numFreqs[i]);
+ writeToFile(freqsLine(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());
+ actualFreqs = mReader.readFreqs(mPowerProfile);
+ assertArrayEquals(freqs[i], actualFreqs);
+ assertTrue(errMsg, mReader.perClusterTimesAvailable());
+ }
+ }
+
+ @Test
+ public void testReadDelta() throws Exception {
+ 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));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < mUids.length; ++i) {
+ mCallback.verify(mUids[i], times[i]);
+ }
+ mCallback.verifyNoMoreInteractions();
+
+ // Verify that readDelta also reads the frequencies if not already available.
+ assertTrue(mTestFile.delete());
+ 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));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < mUids.length; ++i) {
+ mCallback.verify(mUids[i], subtract(newTimes1[i], times[i]));
+ }
+ mCallback.verifyNoMoreInteractions();
+
+ // Verify that there won't be a callback if the proc file values didn't change.
+ mCallback.clear();
+ mReader.readDelta(mCallback);
+ mCallback.verifyNoMoreInteractions();
+
+ // 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));
+ mReader.readDelta(null);
+ mCallback.verifyNoMoreInteractions();
+
+ // Verify that the readDelta call will only return deltas when
+ // the previous call had null callback.
+ mCallback.clear();
+ final long[][] newTimes3 = increaseTime(newTimes2);
+ writeToFile(freqsLine(freqs) + uidLines(mUids, 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());
+ }
+
+ @Test
+ public void testReadAbsolute() throws Exception {
+ 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));
+ mReader.readAbsolute(mCallback);
+ for (int i = 0; i < mUids.length; i++) {
+ mCallback.verify(mUids[i], times1[i]);
+ }
+ mCallback.verifyNoMoreInteractions();
+
+ // Verify that readDelta also reads the frequencies if not already available.
+ assertTrue(mTestFile.delete());
+ 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));
+ mReader.readAbsolute(mCallback);
+ for (int i = 0; i < mUids.length; i++) {
+ mCallback.verify(mUids[i], times2[i]);
+ }
+ mCallback.verifyNoMoreInteractions();
+ assertTrue(mTestFile.delete());
+ }
+
+ @Test
+ public void testReadDeltaWrongData() throws Exception {
+ 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));
+ 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));
+ mReader.readDelta(mCallback);
+ for (int i = 1; i < mUids.length; i++) {
+ mCallback.verify(mUids[i], subtract(times2[i], times1[i]));
+ }
+ mCallback.verifyNoMoreInteractions();
+
+ // Verify that the internal state was not modified.
+ mCallback.clear();
+ final long[][] times3 = increaseTime(times2);
+ times3[0] = increaseTime(times1)[0];
+ writeToFile(freqsLine(freqs) + uidLines(mUids, times3));
+ mReader.readDelta(mCallback);
+ mCallback.verify(mUids[0], subtract(times3[0], times1[0]));
+ for (int i = 1; i < mUids.length; i++) {
+ mCallback.verify(mUids[i], subtract(times3[i], times2[i]));
+ }
+ mCallback.verifyNoMoreInteractions();
+
+ // Verify that there is no callback if any value in the proc file is -ve.
+ mCallback.clear();
+ final long[][] times4 = increaseTime(times3);
+ times4[0][0] *= -1;
+ writeToFile(freqsLine(freqs) + uidLines(mUids, times4));
+ mReader.readDelta(mCallback);
+ for (int i = 1; i < mUids.length; ++i) {
+ mCallback.verify(mUids[i], subtract(times4[i], times3[i]));
+ }
+ mCallback.verifyNoMoreInteractions();
+
+ // Verify that the internal state was not modified when the proc file had -ve value.
+ mCallback.clear();
+ final long[][] times5 = increaseTime(times4);
+ times5[0] = increaseTime(times3)[0];
+ writeToFile(freqsLine(freqs) + uidLines(mUids, 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());
+ }
+
+ private String freqsLine(long[] freqs) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("uid:");
+ for (int i = 0; i < freqs.length; ++i) {
+ sb.append(" " + freqs[i]);
+ }
+ return sb.append('\n').toString();
+ }
+
+ private void setCpuClusterFreqs(int numClusters, int... clusterFreqs) {
+ assertEquals(numClusters, clusterFreqs.length);
+ when(mPowerProfile.getNumCpuClusters()).thenReturn(numClusters);
+ for (int i = 0; i < numClusters; ++i) {
+ when(mPowerProfile.getNumSpeedStepsInCpuCluster(i)).thenReturn(clusterFreqs[i]);
+ }
+ }
+
+ private String uidLines(int[] uids, long[][] times) {
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < uids.length; i++) {
+ sb.append(uids[i]).append(':');
+ for (int j = 0; j < times[i].length; j++) {
+ sb.append(' ').append(times[i][j] / 10);
+ }
+ sb.append('\n');
+ }
+ return sb.toString();
+ }
+
+ private void writeToFile(String s) throws IOException {
+ try (BufferedWriter w = Files.newBufferedWriter(mTestFile.toPath())) {
+ w.write(s);
+ w.flush();
+ }
+ }
+
+ private long[][] increaseTime(long[][] original) {
+ long[][] newTime = new long[original.length][original[0].length];
+ for (int i = 0; i < original.length; i++) {
+ for (int j = 0; j < original[0].length; j++) {
+ newTime[i][j] = original[i][j] + mRand.nextInt(10000) * 10 + 10;
+ }
+ }
+ return newTime;
+ }
+
+ private long[] subtract(long[] a1, long[] a2) {
+ long[] val = new long[a1.length];
+ for (int i = 0; i < val.length; ++i) {
+ val[i] = a1[i] - a2[i];
+ }
+ return val;
+ }
+
+ private class VerifiableCallback implements KernelCpuUidTimeReader.Callback<long[]> {
+ SparseArray<long[]> mData = new SparseArray<>();
+
+ public void verify(int uid, long[] cpuTimes) {
+ long[] array = mData.get(uid);
+ assertNotNull(array);
+ assertArrayEquals(cpuTimes, array);
+ mData.remove(uid);
+ }
+
+ public void clear() {
+ mData.clear();
+ }
+
+ @Override
+ public void onUidCpuTime(int uid, long[] times) {
+ long[] array = new long[times.length];
+ System.arraycopy(times, 0, array, 0, array.length);
+ mData.put(uid, array);
+ }
+
+ public void verifyNoMoreInteractions() {
+ assertEquals(0, mData.size());
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java
new file mode 100644
index 000000000000..9b4512b8b9bd
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2018 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.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.os.FileUtils;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
+
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.Random;
+
+/**
+ * Test class for {@link KernelCpuUidUserSysTimeReader}.
+ *
+ * $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuUidUserSysTimeReaderTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KernelCpuUidUserSysTimeReaderTest {
+ private File mTestDir;
+ private File mTestFile;
+ private KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader mReader;
+ private VerifiableCallback mCallback;
+
+ private Random mRand = new Random(12345);
+ private final int[] mUids = {0, 1, 22, 333, 4444, 55555};
+ private final long[][] mInitialTimes = new long[][]{
+ {15334000, 310964000},
+ {537000, 114000},
+ {40000, 10000},
+ {170000, 57000},
+ {5377000, 867000},
+ {47000, 17000}
+ };
+
+ private Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+
+ @Before
+ public void setUp() {
+ mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
+ mTestFile = new File(mTestDir, "test.file");
+ mReader = new KernelCpuUidUserSysTimeReader(
+ new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+ mCallback = new VerifiableCallback();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ FileUtils.deleteContents(mTestDir);
+ FileUtils.deleteContents(getContext().getFilesDir());
+ }
+
+ @Test
+ public void testThrottler() throws Exception {
+ mReader = new KernelCpuUidUserSysTimeReader(
+ new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), true);
+ mReader.setThrottle(500);
+
+ writeToFile(uidLines(mUids, mInitialTimes));
+ mReader.readDelta(mCallback);
+ assertEquals(6, mCallback.mData.size());
+
+ long[][] times1 = increaseTime(mInitialTimes);
+ writeToFile(uidLines(mUids, times1));
+ mCallback.clear();
+ mReader.readDelta(mCallback);
+ assertEquals(0, mCallback.mData.size());
+
+ SystemClock.sleep(600);
+
+ long[][] times2 = increaseTime(times1);
+ writeToFile(uidLines(mUids, times2));
+ mCallback.clear();
+ mReader.readDelta(mCallback);
+ assertEquals(6, mCallback.mData.size());
+
+ long[][] times3 = increaseTime(times2);
+ writeToFile(uidLines(mUids, times3));
+ mCallback.clear();
+ mReader.readDelta(mCallback);
+ assertEquals(0, mCallback.mData.size());
+ }
+
+ @Test
+ public void testReadDelta() throws Exception {
+ final long[][] times1 = mInitialTimes;
+ writeToFile(uidLines(mUids, times1));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < mUids.length; i++) {
+ mCallback.verify(mUids[i], times1[i]);
+ }
+ mCallback.verifyNoMoreInteractions();
+ mCallback.clear();
+
+ // Verify that a second call will only return deltas.
+ final long[][] times2 = increaseTime(times1);
+ writeToFile(uidLines(mUids, times2));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < mUids.length; i++) {
+ mCallback.verify(mUids[i], subtract(times2[i], times1[i]));
+ }
+ mCallback.verifyNoMoreInteractions();
+ mCallback.clear();
+
+ // Verify that there won't be a callback if the proc file values didn't change.
+ mReader.readDelta(mCallback);
+ mCallback.verifyNoMoreInteractions();
+ mCallback.clear();
+
+ // Verify that calling with a null callback doesn't result in any crashes
+ final long[][] times3 = increaseTime(times2);
+ writeToFile(uidLines(mUids, times3));
+ mReader.readDelta(null);
+
+ // Verify that the readDelta call will only return deltas when
+ // the previous call had null callback.
+ final long[][] times4 = increaseTime(times3);
+ writeToFile(uidLines(mUids, times4));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < mUids.length; i++) {
+ mCallback.verify(mUids[i], subtract(times4[i], times3[i]));
+ }
+ mCallback.verifyNoMoreInteractions();
+ mCallback.clear();
+ assertTrue(mTestFile.delete());
+ }
+
+ @Test
+ public void testReadDeltaWrongData() throws Exception {
+ final long[][] times1 = mInitialTimes;
+ writeToFile(uidLines(mUids, times1));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < mUids.length; i++) {
+ mCallback.verify(mUids[i], times1[i]);
+ }
+ mCallback.verifyNoMoreInteractions();
+ mCallback.clear();
+
+ // Verify that there should not be a callback for a particular UID if its time decreases.
+ final long[][] times2 = increaseTime(times1);
+ times2[0][0] = 1000;
+ writeToFile(uidLines(mUids, times2));
+ mReader.readDelta(mCallback);
+ for (int i = 1; i < mUids.length; i++) {
+ mCallback.verify(mUids[i], subtract(times2[i], times1[i]));
+ }
+ mCallback.verifyNoMoreInteractions();
+ mCallback.clear();
+ assertTrue(mTestFile.delete());
+ }
+
+ @Test
+ public void testReadAbsolute() throws Exception {
+ final long[][] times1 = mInitialTimes;
+ writeToFile(uidLines(mUids, times1));
+ mReader.readAbsolute(mCallback);
+ for (int i = 0; i < mUids.length; i++) {
+ mCallback.verify(mUids[i], times1[i]);
+ }
+ mCallback.verifyNoMoreInteractions();
+ mCallback.clear();
+
+ // Verify that a second call should still return absolute values
+ final long[][] times2 = increaseTime(times1);
+ writeToFile(uidLines(mUids, times2));
+ mReader.readAbsolute(mCallback);
+ for (int i = 0; i < mUids.length; i++) {
+ mCallback.verify(mUids[i], times2[i]);
+ }
+ mCallback.verifyNoMoreInteractions();
+ mCallback.clear();
+ assertTrue(mTestFile.delete());
+ }
+
+ private String uidLines(int[] uids, long[][] times) {
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < uids.length; i++) {
+ sb.append(uids[i]).append(':');
+ for (int j = 0; j < times[i].length; j++) {
+ sb.append(' ').append(times[i][j]);
+ }
+ sb.append('\n');
+ }
+ return sb.toString();
+ }
+
+ private void writeToFile(String s) throws IOException {
+ try (BufferedWriter w = Files.newBufferedWriter(mTestFile.toPath())) {
+ w.write(s);
+ w.flush();
+ }
+ }
+
+ private long[][] increaseTime(long[][] original) {
+ long[][] newTime = new long[original.length][original[0].length];
+ for (int i = 0; i < original.length; i++) {
+ for (int j = 0; j < original[0].length; j++) {
+ newTime[i][j] = original[i][j] + mRand.nextInt(1000) * 1000 + 1000;
+ }
+ }
+ return newTime;
+ }
+
+ private long[] subtract(long[] a1, long[] a2) {
+ long[] val = new long[a1.length];
+ for (int i = 0; i < val.length; ++i) {
+ val[i] = a1[i] - a2[i];
+ }
+ return val;
+ }
+
+ private class VerifiableCallback implements KernelCpuUidTimeReader.Callback<long[]> {
+ SparseArray<long[]> mData = new SparseArray<>();
+
+ public void verify(int uid, long[] cpuTimes) {
+ long[] array = mData.get(uid);
+ assertNotNull(array);
+ assertArrayEquals(cpuTimes, array);
+ mData.remove(uid);
+ }
+
+ public void clear() {
+ mData.clear();
+ }
+
+ @Override
+ public void onUidCpuTime(int uid, long[] times) {
+ long[] array = new long[times.length];
+ System.arraycopy(times, 0, array, 0, array.length);
+ mData.put(uid, array);
+ }
+
+ public void verifyNoMoreInteractions() {
+ assertEquals(0, mData.size());
+ }
+ }
+}