import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
@VisibleForTesting
public void readKernelUidCpuActiveTimesLocked(boolean onBattery) {
final long startTimeMs = mClocks.uptimeMillis();
- mKernelUidCpuActiveTimeReader.readDelta((uid, cpuActiveTimesUs) -> {
+ mKernelUidCpuActiveTimeReader.readDelta((uid, cpuActiveTimesMs) -> {
uid = mapUid(uid);
if (Process.isIsolated(uid)) {
mKernelUidCpuActiveTimeReader.removeUid(uid);
return;
}
final Uid u = getUidStatsLocked(uid);
- u.mCpuActiveTimeMs.addCountLocked(cpuActiveTimesUs, onBattery);
+ u.mCpuActiveTimeMs.addCountLocked(cpuActiveTimesMs, onBattery);
});
final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs;
@VisibleForTesting
public void readKernelUidCpuClusterTimesLocked(boolean onBattery) {
final long startTimeMs = mClocks.uptimeMillis();
- mKernelUidCpuClusterTimeReader.readDelta((uid, cpuClusterTimesUs) -> {
+ mKernelUidCpuClusterTimeReader.readDelta((uid, cpuClusterTimesMs) -> {
uid = mapUid(uid);
if (Process.isIsolated(uid)) {
mKernelUidCpuClusterTimeReader.removeUid(uid);
return;
}
final Uid u = getUidStatsLocked(uid);
- u.mCpuClusterTimesMs.addCountLocked(cpuClusterTimesUs, onBattery);
+ u.mCpuClusterTimesMs.addCountLocked(cpuClusterTimesMs, onBattery);
});
final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs;
= "read_binary_cpu_time";
public static final String KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS
= "proc_state_cpu_times_read_delay_ms";
+ public static final String KEY_KERNEL_UID_READERS_THROTTLE_TIME
+ = "kernel_uid_readers_throttle_time";
private static final boolean DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE = true;
private static final boolean DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME = true;
private static final boolean DEFAULT_READ_BINARY_CPU_TIME = false;
private static final long DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS = 5_000;
+ private static final long DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME = 10_000;
public boolean TRACK_CPU_TIMES_BY_PROC_STATE = DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE;
public boolean TRACK_CPU_ACTIVE_CLUSTER_TIME = DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME;
- // Not used right now.
public boolean READ_BINARY_CPU_TIME = DEFAULT_READ_BINARY_CPU_TIME;
public long PROC_STATE_CPU_TIMES_READ_DELAY_MS = DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS;
+ public long KERNEL_UID_READERS_THROTTLE_TIME = DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME;
private ContentResolver mResolver;
private final KeyValueListParser mParser = new KeyValueListParser(',');
DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE));
TRACK_CPU_ACTIVE_CLUSTER_TIME = mParser.getBoolean(
KEY_TRACK_CPU_ACTIVE_CLUSTER_TIME, DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME);
- READ_BINARY_CPU_TIME = mParser.getBoolean(
- KEY_READ_BINARY_CPU_TIME, DEFAULT_READ_BINARY_CPU_TIME);
+ updateReadBinaryCpuTime(READ_BINARY_CPU_TIME,
+ mParser.getBoolean(KEY_READ_BINARY_CPU_TIME, DEFAULT_READ_BINARY_CPU_TIME));
updateProcStateCpuTimesReadDelayMs(PROC_STATE_CPU_TIMES_READ_DELAY_MS,
mParser.getLong(KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS,
- DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS));
+ DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS));
+ updateKernelUidReadersThrottleTime(KERNEL_UID_READERS_THROTTLE_TIME,
+ mParser.getLong(KEY_KERNEL_UID_READERS_THROTTLE_TIME,
+ DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME));
}
}
}
}
+ private void updateReadBinaryCpuTime(boolean oldEnabled, boolean isEnabled) {
+ READ_BINARY_CPU_TIME = isEnabled;
+ if (oldEnabled != isEnabled) {
+ mKernelUidCpuFreqTimeReader.setReadBinary(isEnabled);
+ }
+ }
+
private void updateProcStateCpuTimesReadDelayMs(long oldDelayMillis, long newDelayMillis) {
PROC_STATE_CPU_TIMES_READ_DELAY_MS = newDelayMillis;
if (oldDelayMillis != newDelayMillis) {
}
}
+ private void updateKernelUidReadersThrottleTime(long oldTimeMs, long newTimeMs) {
+ KERNEL_UID_READERS_THROTTLE_TIME = newTimeMs;
+ if (oldTimeMs != newTimeMs) {
+ mKernelUidCpuFreqTimeReader.setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME);
+ mKernelUidCpuActiveTimeReader.setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME);
+ mKernelUidCpuClusterTimeReader
+ .setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME);
+ }
+ }
+
public void dumpLocked(PrintWriter pw) {
pw.print(KEY_TRACK_CPU_TIMES_BY_PROC_STATE); pw.print("=");
pw.println(TRACK_CPU_TIMES_BY_PROC_STATE);
pw.println(READ_BINARY_CPU_TIME);
pw.print(KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS); pw.print("=");
pw.println(PROC_STATE_CPU_TIMES_READ_DELAY_MS);
+ pw.print(KEY_KERNEL_UID_READERS_THROTTLE_TIME); pw.print("=");
+ pw.println(KERNEL_UID_READERS_THROTTLE_TIME);
}
}
cpuPowerMaUs += cpuSpeedStepPower;
}
}
- cpuPowerMaUs += u.getCpuActiveTime() * mProfile.getAveragePower(
+ cpuPowerMaUs += u.getCpuActiveTime() * 1000 * mProfile.getAveragePower(
PowerProfile.POWER_CPU_ACTIVE);
long[] cpuClusterTimes = u.getCpuClusterTimes();
if (cpuClusterTimes != null) {
if (cpuClusterTimes.length == numClusters) {
for (int i = 0; i < numClusters; i++) {
- double power = cpuClusterTimes[i] * mProfile.getAveragePowerForCpuCluster(i);
+ double power =
+ cpuClusterTimes[i] * 1000 * mProfile.getAveragePowerForCpuCluster(i);
cpuPowerMaUs += power;
if (DEBUG) {
Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + i + " clusterTimeUs="
--- /dev/null
+/*
+ * 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 android.os.StrictMode;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+
+/**
+ * Reads cpu time proc files with throttling (adjustable interval).
+ *
+ * KernelCpuProcReader is implemented as singletons for built-in kernel proc files. Get___Instance()
+ * method will return corresponding reader instance. In order to prevent frequent GC,
+ * KernelCpuProcReader reuses a {@link ByteBuffer} to store data read from proc files.
+ *
+ * A KernelCpuProcReader 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.
+ *
+ * Each KernelCpuProcReader instance also has a throttler. Throttle interval can be adjusted via
+ * {@link #setThrottleInterval(long)} method. Default throttle interval is 3000ms. If current
+ * timestamp based on {@link SystemClock#elapsedRealtime()} is less than throttle interval from
+ * the last read timestamp, {@link #readBytes()} will return previous result.
+ *
+ * A KernelCpuProcReader instance is thread-unsafe. Caller needs to hold a lock on this object while
+ * accessing its instance methods or digesting the return values.
+ */
+public class KernelCpuProcReader {
+ private static final String TAG = "KernelCpuProcReader";
+ private static final int ERROR_THRESHOLD = 5;
+ // Throttle interval in milliseconds
+ private static final long DEFAULT_THROTTLE_INTERVAL = 3000L;
+ private static final int INITIAL_BUFFER_SIZE = 8 * 1024;
+ private static final int MAX_BUFFER_SIZE = 1024 * 1024;
+ private static final String PROC_UID_FREQ_TIME = "/proc/uid_cpupower/time_in_state";
+ private static final String PROC_UID_ACTIVE_TIME = "/proc/uid_cpupower/concurrent_active_time";
+ private static final String PROC_UID_CLUSTER_TIME = "/proc/uid_cpupower/concurrent_policy_time";
+
+ private static final KernelCpuProcReader mFreqTimeReader = new KernelCpuProcReader(
+ PROC_UID_FREQ_TIME);
+ private static final KernelCpuProcReader mActiveTimeReader = new KernelCpuProcReader(
+ PROC_UID_ACTIVE_TIME);
+ private static final KernelCpuProcReader mClusterTimeReader = new KernelCpuProcReader(
+ PROC_UID_CLUSTER_TIME);
+
+ public static KernelCpuProcReader getFreqTimeReaderInstance() {
+ return mFreqTimeReader;
+ }
+
+ public static KernelCpuProcReader getActiveTimeReaderInstance() {
+ return mActiveTimeReader;
+ }
+
+ public static KernelCpuProcReader getClusterTimeReaderInstance() {
+ return mClusterTimeReader;
+ }
+
+ private int mErrors;
+ private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL;
+ private long mLastReadTime = Long.MIN_VALUE;
+ private final Path mProc;
+ private ByteBuffer mBuffer;
+
+ @VisibleForTesting
+ public KernelCpuProcReader(String procFile) {
+ mProc = Paths.get(procFile);
+ mBuffer = ByteBuffer.allocateDirect(INITIAL_BUFFER_SIZE);
+ mBuffer.clear();
+ }
+
+ /**
+ * Reads all bytes from the corresponding proc file.
+ *
+ * If elapsed time since last call to this method is less than the throttle interval, it will
+ * return previous result. When IOException accumulates to 5, it will always return null. This
+ * method is thread-unsafe, so is the return value. Caller needs to hold a lock on this
+ * object while calling this method and digesting its return value.
+ *
+ * @return a {@link ByteBuffer} containing all bytes from the proc file.
+ */
+ public ByteBuffer readBytes() {
+ if (mErrors >= ERROR_THRESHOLD) {
+ return null;
+ }
+ if (SystemClock.elapsedRealtime() < mLastReadTime + mThrottleInterval) {
+ if (mBuffer.limit() > 0 && mBuffer.limit() < mBuffer.capacity()) {
+ // mBuffer has data.
+ return mBuffer.asReadOnlyBuffer().order(ByteOrder.nativeOrder());
+ }
+ return null;
+ }
+ mLastReadTime = SystemClock.elapsedRealtime();
+ mBuffer.clear();
+ final int oldMask = StrictMode.allowThreadDiskReadsMask();
+ try (FileChannel fc = FileChannel.open(mProc, StandardOpenOption.READ)) {
+ while (fc.read(mBuffer) == mBuffer.capacity()) {
+ if (!resize()) {
+ mErrors++;
+ Slog.e(TAG, "Proc file is too large: " + mProc);
+ return null;
+ }
+ fc.position(0);
+ }
+ } catch (IOException e) {
+ mErrors++;
+ Slog.e(TAG, "Error reading: " + mProc, e);
+ return null;
+ } finally {
+ StrictMode.setThreadPolicyMask(oldMask);
+ }
+ mBuffer.flip();
+ return mBuffer.asReadOnlyBuffer().order(ByteOrder.nativeOrder());
+ }
+
+ /**
+ * Sets the throttle interval. Set to 0 will disable throttling. Thread-unsafe, holding a lock
+ * on this object is recommended.
+ *
+ * @param throttleInterval throttle interval in milliseconds
+ */
+ public void setThrottleInterval(long throttleInterval) {
+ if (throttleInterval >= 0) {
+ mThrottleInterval = throttleInterval;
+ }
+ }
+
+ private boolean resize() {
+ if (mBuffer.capacity() >= MAX_BUFFER_SIZE) {
+ return false;
+ }
+ int newSize = Math.min(mBuffer.capacity() << 1, MAX_BUFFER_SIZE);
+ // Slog.i(TAG, "Resize buffer " + mBuffer.capacity() + " => " + newSize);
+ mBuffer = ByteBuffer.allocateDirect(newSize);
+ return true;
+ }
+}
package com.android.internal.os;
import android.annotation.Nullable;
-import android.os.StrictMode;
import android.os.SystemClock;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TimeUtils;
import com.android.internal.annotations.VisibleForTesting;
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
/**
- * Reads /proc/uid_concurrent_active_time which has the format:
- * active: X (X is # cores)
- * [uid0]: [time-0] [time-1] [time-2] ... (# entries = # cores)
- * [uid1]: [time-0] [time-1] [time-2] ... ...
+ * Reads binary proc file /proc/uid_cpupower/concurrent_active_time and reports CPU active time to
+ * BatteryStats to compute {@link PowerProfile#POWER_CPU_ACTIVE}.
+ *
+ * concurrent_active_time is an array of u32's in the following format:
+ * [n, uid0, time0a, time0b, ..., time0n,
+ * uid1, time1a, time1b, ..., time1n,
+ * uid2, time2a, time2b, ..., time2n, etc.]
+ * where n is the total number of cpus (num_possible_cpus)
* ...
- * Time-N means the CPU time a UID spent running concurrently with N other processes.
+ * 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.
+ *
+ * This class uses a throttler to reject any {@link #readDelta} call within
+ * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader},
+ * which has a shorter throttle interval and returns cached result from last read when the request
+ * is throttled.
+ *
+ * This class is NOT thread-safe and NOT designed to be accessed by more than one caller (due to
+ * the nature of {@link #readDelta(Callback)}).
*/
public class KernelUidCpuActiveTimeReader {
- private static final boolean DEBUG = false;
private static final String TAG = "KernelUidCpuActiveTimeReader";
- private static final String UID_TIMES_PROC_FILE = "/proc/uid_concurrent_active_time";
+ // Throttle interval in milliseconds
+ private static final long DEFAULT_THROTTLE_INTERVAL = 10_000L;
- private int mCoreCount;
- private long mLastTimeReadMs;
- private long mNowTimeMs;
- private SparseArray<long[]> mLastUidCpuActiveTimeMs = new SparseArray<>();
+ private final KernelCpuProcReader mProcReader;
+ private long mLastTimeReadMs = Long.MIN_VALUE;
+ private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL;
+ private SparseArray<Double> mLastUidCpuActiveTimeMs = new SparseArray<>();
public interface Callback {
+ /**
+ * Notifies when new data is available.
+ *
+ * @param uid uid int
+ * @param cpuActiveTimeMs cpu active time spent by this uid in milliseconds
+ */
void onUidCpuActiveTime(int uid, long cpuActiveTimeMs);
}
+ public KernelUidCpuActiveTimeReader() {
+ mProcReader = KernelCpuProcReader.getActiveTimeReaderInstance();
+ }
+
+ @VisibleForTesting
+ public KernelUidCpuActiveTimeReader(KernelCpuProcReader procReader) {
+ mProcReader = procReader;
+ }
+
public void readDelta(@Nullable Callback cb) {
- final int oldMask = StrictMode.allowThreadDiskReadsMask();
- try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
- mNowTimeMs = SystemClock.elapsedRealtime();
- readDeltaInternal(reader, cb);
- mLastTimeReadMs = mNowTimeMs;
- } catch (IOException e) {
- Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
- } finally {
- StrictMode.setThreadPolicyMask(oldMask);
+ if (SystemClock.elapsedRealtime() < mLastTimeReadMs + mThrottleInterval) {
+ Slog.w(TAG, "Throttle");
+ return;
+ }
+ synchronized (mProcReader) {
+ final ByteBuffer bytes = mProcReader.readBytes();
+ if (bytes == null || bytes.remaining() <= 4) {
+ // Error already logged in mProcReader.
+ return;
+ }
+ if ((bytes.remaining() & 3) != 0) {
+ Slog.wtf(TAG,
+ "Cannot parse active time proc bytes to int: " + bytes.remaining());
+ return;
+ }
+ final IntBuffer buf = bytes.asIntBuffer();
+ final int cores = buf.get();
+ if (cores <= 0 || buf.remaining() % (cores + 1) != 0) {
+ Slog.wtf(TAG,
+ "Cpu active time format error: " + buf.remaining() + " / " + (cores
+ + 1));
+ return;
+ }
+ int numUids = buf.remaining() / (cores + 1);
+ for (int i = 0; i < numUids; i++) {
+ int uid = buf.get();
+ boolean corrupted = false;
+ double curTime = 0;
+ for (int j = 1; j <= cores; j++) {
+ int time = buf.get();
+ if (time < 0) {
+ Slog.e(TAG, "Corrupted data from active time proc: " + time);
+ corrupted = true;
+ } else {
+ curTime += (double) time * 10 / j; // Unit is 10ms.
+ }
+ }
+ double delta = curTime - mLastUidCpuActiveTimeMs.get(uid, 0.0);
+ if (delta > 0 && !corrupted) {
+ mLastUidCpuActiveTimeMs.put(uid, curTime);
+ if (cb != null) {
+ cb.onUidCpuActiveTime(uid, (long) delta);
+ }
+ }
+ }
+ // Slog.i(TAG, "Read uids: " + numUids);
+ }
+ mLastTimeReadMs = SystemClock.elapsedRealtime();
+ }
+
+ public void setThrottleInterval(long throttleInterval) {
+ if (throttleInterval >= 0) {
+ mThrottleInterval = throttleInterval;
}
}
final int lastIndex = mLastUidCpuActiveTimeMs.indexOfKey(endUid);
mLastUidCpuActiveTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
}
-
- @VisibleForTesting
- public void readDeltaInternal(BufferedReader reader, @Nullable Callback cb) throws IOException {
- String line = reader.readLine();
- if (line == null || !line.startsWith("active:")) {
- Slog.e(TAG, String.format("Malformed proc file: %s ", UID_TIMES_PROC_FILE));
- return;
- }
- if (mCoreCount == 0) {
- mCoreCount = Integer.parseInt(line.substring(line.indexOf(' ')+1));
- }
- while ((line = reader.readLine()) != null) {
- final int index = line.indexOf(' ');
- final int uid = Integer.parseInt(line.substring(0, index - 1), 10);
- readTimesForUid(uid, line.substring(index + 1), cb);
- }
- }
-
- private void readTimesForUid(int uid, String line, @Nullable Callback cb) {
- long[] lastActiveTime = mLastUidCpuActiveTimeMs.get(uid);
- if (lastActiveTime == null) {
- lastActiveTime = new long[mCoreCount];
- mLastUidCpuActiveTimeMs.put(uid, lastActiveTime);
- }
- final String[] timesStr = line.split(" ");
- if (timesStr.length != mCoreCount) {
- Slog.e(TAG, String.format("# readings don't match # cores, readings: %d, CPU cores: %d",
- timesStr.length, mCoreCount));
- return;
- }
- long sumDeltas = 0;
- final long[] curActiveTime = new long[mCoreCount];
- boolean notify = false;
- for (int i = 0; i < mCoreCount; i++) {
- // Times read will be in units of 10ms
- curActiveTime[i] = Long.parseLong(timesStr[i], 10) * 10;
- long delta = curActiveTime[i] - lastActiveTime[i];
- if (delta < 0 || curActiveTime[i] < 0) {
- if (DEBUG) {
- final StringBuilder sb = new StringBuilder();
- sb.append(String.format("Malformed cpu active time for UID=%d\n", uid));
- sb.append(String.format("data=(%d,%d)\n", lastActiveTime[i], curActiveTime[i]));
- sb.append("times=(");
- TimeUtils.formatDuration(mLastTimeReadMs, sb);
- sb.append(",");
- TimeUtils.formatDuration(mNowTimeMs, sb);
- sb.append(")");
- Slog.e(TAG, sb.toString());
- }
- return;
- }
- notify |= delta > 0;
- sumDeltas += delta / (i + 1);
- }
- if (notify) {
- System.arraycopy(curActiveTime, 0, lastActiveTime, 0, mCoreCount);
- if (cb != null) {
- cb.onUidCpuActiveTime(uid, sumDeltas);
- }
- }
- }
}
package com.android.internal.os;
import android.annotation.Nullable;
-import android.os.StrictMode;
import android.os.SystemClock;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TimeUtils;
import com.android.internal.annotations.VisibleForTesting;
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
/**
- * Reads /proc/uid_concurrent_policy_time which has the format:
- * policy0: X policy4: Y (there are X cores on policy0, Y cores on policy4)
- * [uid0]: [time-0-0] [time-0-1] ... [time-1-0] [time-1-1] ...
- * [uid1]: [time-0-0] [time-0-1] ... [time-1-0] [time-1-1] ...
- * ...
- * Time-X-Y means the time a UID spent on clusterX running concurrently with Y other processes.
+ * Reads binary proc file /proc/uid_cpupower/concurrent_policy_time and reports CPU cluster times
+ * to BatteryStats to compute cluster power. See
+ * {@link PowerProfile#getAveragePowerForCpuCluster(int)}.
+ *
+ * concurrent_policy_time is an array of u32's in the following format:
+ * [n, x0, ..., xn, uid0, time0a, time0b, ..., time0n,
+ * uid1, time1a, time1b, ..., time1n,
+ * uid2, time2a, time2b, ..., time2n, etc.]
+ * where n is the number of policies
+ * xi is the number cpus on a particular policy
+ * Each uidX is followed by x0 time entries corresponding to the time UID X spent on cluster0
+ * running concurrently with 0, 1, 2, ..., x0 - 1 other processes, then followed by x1, ..., xn
+ * 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.
+ * maintains the previous results of a call to {@link #readDelta} in order to provide a
+ * proper delta.
+ *
+ * This class uses a throttler to reject any {@link #readDelta} call within
+ * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader},
+ * which has a shorter throttle interval and returns cached result from last read when the request
+ * is throttled.
+ *
+ * This class is NOT thread-safe and NOT designed to be accessed by more than one caller (due to
+ * the nature of {@link #readDelta(Callback)}).
*/
public class KernelUidCpuClusterTimeReader {
-
- private static final boolean DEBUG = false;
private static final String TAG = "KernelUidCpuClusterTimeReader";
- private static final String UID_TIMES_PROC_FILE = "/proc/uid_concurrent_policy_time";
+ // Throttle interval in milliseconds
+ private static final long DEFAULT_THROTTLE_INTERVAL = 10_000L;
+
+ private final KernelCpuProcReader mProcReader;
+ private long mLastTimeReadMs = Long.MIN_VALUE;
+ private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL;
+ private SparseArray<double[]> mLastUidPolicyTimeMs = new SparseArray<>();
- // mCoreOnCluster[i] is the # of cores on cluster i
- private int[] mCoreOnCluster;
- private int mCores;
- private long mLastTimeReadMs;
- private long mNowTimeMs;
- private SparseArray<long[]> mLastUidPolicyTimeMs = new SparseArray<>();
+ private int mNumClusters = -1;
+ private int mNumCores;
+ private int[] mNumCoresOnCluster;
+
+ private double[] mCurTime; // Reuse to avoid GC.
+ private long[] mDeltaTime; // Reuse to avoid GC.
public interface Callback {
/**
- * @param uid
- * @param cpuActiveTimeMs the first dimension is cluster, the second dimension is the # of
- * processes running concurrently with this uid.
+ * Notifies when new data is available.
+ *
+ * @param uid uid int
+ * @param cpuClusterTimeMs an array of times spent by this uid on corresponding clusters.
+ * The array index is the cluster index.
*/
- void onUidCpuPolicyTime(int uid, long[] cpuActiveTimeMs);
+ void onUidCpuPolicyTime(int uid, long[] cpuClusterTimeMs);
}
- public void readDelta(@Nullable Callback cb) {
- final int oldMask = StrictMode.allowThreadDiskReadsMask();
- try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
- mNowTimeMs = SystemClock.elapsedRealtime();
- readDeltaInternal(reader, cb);
- mLastTimeReadMs = mNowTimeMs;
- } catch (IOException e) {
- Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
- } finally {
- StrictMode.setThreadPolicyMask(oldMask);
- }
+ public KernelUidCpuClusterTimeReader() {
+ mProcReader = KernelCpuProcReader.getClusterTimeReaderInstance();
}
- public void removeUid(int uid) {
- mLastUidPolicyTimeMs.delete(uid);
+ @VisibleForTesting
+ public KernelUidCpuClusterTimeReader(KernelCpuProcReader procReader) {
+ mProcReader = procReader;
}
- public void removeUidsInRange(int startUid, int endUid) {
- if (endUid < startUid) {
- Slog.w(TAG, "End UID " + endUid + " is smaller than start UID " + startUid);
- return;
+ public void setThrottleInterval(long throttleInterval) {
+ if (throttleInterval >= 0) {
+ mThrottleInterval = throttleInterval;
}
- mLastUidPolicyTimeMs.put(startUid, null);
- mLastUidPolicyTimeMs.put(endUid, null);
- final int firstIndex = mLastUidPolicyTimeMs.indexOfKey(startUid);
- final int lastIndex = mLastUidPolicyTimeMs.indexOfKey(endUid);
- mLastUidPolicyTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
}
- @VisibleForTesting
- public void readDeltaInternal(BufferedReader reader, @Nullable Callback cb) throws IOException {
- String line = reader.readLine();
- if (line == null || !line.startsWith("policy")) {
- Slog.e(TAG, String.format("Malformed proc file: %s ", UID_TIMES_PROC_FILE));
+ public void readDelta(@Nullable Callback cb) {
+ if (SystemClock.elapsedRealtime() < mLastTimeReadMs + mThrottleInterval) {
+ Slog.w(TAG, "Throttle");
return;
}
- if (mCoreOnCluster == null) {
- List<Integer> list = new ArrayList<>();
- String[] policies = line.split(" ");
-
- if (policies.length == 0 || policies.length % 2 != 0) {
- Slog.e(TAG, String.format("Malformed proc file: %s ", UID_TIMES_PROC_FILE));
+ synchronized (mProcReader) {
+ ByteBuffer bytes = mProcReader.readBytes();
+ if (bytes == null || bytes.remaining() <= 4) {
+ // Error already logged in mProcReader.
+ return;
+ }
+ if ((bytes.remaining() & 3) != 0) {
+ Slog.wtf(TAG,
+ "Cannot parse cluster time proc bytes to int: " + bytes.remaining());
+ return;
+ }
+ IntBuffer buf = bytes.asIntBuffer();
+ final int numClusters = buf.get();
+ if (numClusters <= 0) {
+ Slog.wtf(TAG, "Cluster time format error: " + numClusters);
+ return;
+ }
+ if (mNumClusters == -1) {
+ mNumClusters = numClusters;
+ }
+ if (buf.remaining() < numClusters) {
+ Slog.wtf(TAG, "Too few data left in the buffer: " + buf.remaining());
return;
}
+ if (mNumCores <= 0) {
+ if (!readCoreInfo(buf, numClusters)) {
+ return;
+ }
+ } else {
+ buf.position(buf.position() + numClusters);
+ }
- for (int i = 0; i < policies.length; i+=2) {
- list.add(Integer.parseInt(policies[i+1]));
+ if (buf.remaining() % (mNumCores + 1) != 0) {
+ Slog.wtf(TAG,
+ "Cluster time format error: " + buf.remaining() + " / " + (mNumCores
+ + 1));
+ return;
}
+ int numUids = buf.remaining() / (mNumCores + 1);
- mCoreOnCluster = new int[list.size()];
- for(int i=0;i<list.size();i++){
- mCoreOnCluster[i] = list.get(i);
- mCores += mCoreOnCluster[i];
+ for (int i = 0; i < numUids; i++) {
+ processUidLocked(buf, cb);
}
+ // Slog.i(TAG, "Read uids: " + numUids);
}
- while ((line = reader.readLine()) != null) {
- final int index = line.indexOf(' ');
- final int uid = Integer.parseInt(line.substring(0, index - 1), 10);
- readTimesForUid(uid, line.substring(index + 1), cb);
- }
+ mLastTimeReadMs = SystemClock.elapsedRealtime();
}
- private void readTimesForUid(int uid, String line, @Nullable Callback cb) {
- long[] lastPolicyTime = mLastUidPolicyTimeMs.get(uid);
- if (lastPolicyTime == null) {
- lastPolicyTime = new long[mCores];
- mLastUidPolicyTimeMs.put(uid, lastPolicyTime);
+ private void processUidLocked(IntBuffer buf, @Nullable Callback cb) {
+ int uid = buf.get();
+ double[] lastTimes = mLastUidPolicyTimeMs.get(uid);
+ if (lastTimes == null) {
+ lastTimes = new double[mNumClusters];
+ mLastUidPolicyTimeMs.put(uid, lastTimes);
}
- final String[] timeStr = line.split(" ");
- if (timeStr.length != mCores) {
- Slog.e(TAG, String.format("# readings don't match # cores, readings: %d, # CPU cores: %d",
- timeStr.length, mCores));
- return;
- }
- final long[] deltaPolicyTime = new long[mCores];
- final long[] currPolicyTime = new long[mCores];
+
boolean notify = false;
- for (int i = 0; i < mCores; i++) {
- // Times read will be in units of 10ms
- currPolicyTime[i] = Long.parseLong(timeStr[i], 10) * 10;
- deltaPolicyTime[i] = currPolicyTime[i] - lastPolicyTime[i];
- if (deltaPolicyTime[i] < 0 || currPolicyTime[i] < 0) {
- if (DEBUG) {
- final StringBuilder sb = new StringBuilder();
- sb.append(String.format("Malformed cpu policy time for UID=%d\n", uid));
- sb.append(String.format("data=(%d,%d)\n", lastPolicyTime[i], currPolicyTime[i]));
- sb.append("times=(");
- TimeUtils.formatDuration(mLastTimeReadMs, sb);
- sb.append(",");
- TimeUtils.formatDuration(mNowTimeMs, sb);
- sb.append(")");
- Slog.e(TAG, sb.toString());
+ boolean corrupted = false;
+
+ for (int j = 0; j < mNumClusters; j++) {
+ mCurTime[j] = 0;
+ for (int k = 1; k <= mNumCoresOnCluster[j]; k++) {
+ int time = buf.get();
+ if (time < 0) {
+ Slog.e(TAG, "Corrupted data from cluster time proc uid: " + uid);
+ corrupted = true;
}
- return;
+ mCurTime[j] += (double) time * 10 / k; // Unit is 10ms.
}
- notify |= deltaPolicyTime[i] > 0;
+ mDeltaTime[j] = (long) (mCurTime[j] - lastTimes[j]);
+ if (mDeltaTime[j] < 0) {
+ Slog.e(TAG, "Unexpected delta from cluster time proc uid: " + uid);
+ corrupted = true;
+ }
+ notify |= mDeltaTime[j] > 0;
}
- if (notify) {
- System.arraycopy(currPolicyTime, 0, lastPolicyTime, 0, mCores);
+ if (notify && !corrupted) {
+ System.arraycopy(mCurTime, 0, lastTimes, 0, mNumClusters);
if (cb != null) {
- final long[] times = new long[mCoreOnCluster.length];
- int core = 0;
- for (int i = 0; i < mCoreOnCluster.length; i++) {
- for (int j = 0; j < mCoreOnCluster[i]; j++) {
- times[i] += deltaPolicyTime[core++] / (j+1);
- }
- }
- cb.onUidCpuPolicyTime(uid, times);
+ cb.onUidCpuPolicyTime(uid, mDeltaTime);
}
}
}
+
+ // Returns if it has read valid info.
+ private boolean readCoreInfo(IntBuffer buf, int numClusters) {
+ int numCores = 0;
+ int[] numCoresOnCluster = new int[numClusters];
+ for (int i = 0; i < numClusters; i++) {
+ numCoresOnCluster[i] = buf.get();
+ numCores += numCoresOnCluster[i];
+ }
+ if (numCores <= 0) {
+ Slog.e(TAG, "Invalid # cores from cluster time proc file: " + numCores);
+ return false;
+ }
+ mNumCores = numCores;
+ mNumCoresOnCluster = numCoresOnCluster;
+ mCurTime = new double[numClusters];
+ mDeltaTime = new long[numClusters];
+ return true;
+ }
+
+ public void removeUid(int uid) {
+ mLastUidPolicyTimeMs.delete(uid);
+ }
+
+ public void removeUidsInRange(int startUid, int endUid) {
+ if (endUid < startUid) {
+ Slog.w(TAG, "End UID " + endUid + " is smaller than start UID " + startUid);
+ return;
+ }
+ mLastUidPolicyTimeMs.put(startUid, null);
+ mLastUidPolicyTimeMs.put(endUid, null);
+ final int firstIndex = mLastUidPolicyTimeMs.indexOfKey(startUid);
+ final int lastIndex = mLastUidPolicyTimeMs.indexOfKey(endUid);
+ mLastUidPolicyTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
+ }
}
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
/**
* Reads /proc/uid_time_in_state which has the format:
* [uid2]: [time in freq1] [time in freq2] [time in freq3] ...
* ...
*
+ * Binary variation reads /proc/uid_cpupower/time_in_state in the following format:
+ * [n, uid0, time0a, time0b, ..., time0n,
+ * uid1, time1a, time1b, ..., time1n,
+ * uid2, time2a, time2b, ..., time2n, etc.]
+ * where n is the total number of frequencies.
+ *
* 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.
+ *
+ * This class uses a throttler to reject any {@link #readDelta} call within
+ * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader},
+ * which has a shorter throttle interval and returns cached result from last read when the request
+ * is throttled.
+ *
+ * This class is NOT thread-safe and NOT designed to be accessed by more than one caller (due to
+ * the nature of {@link #readDelta(Callback)}).
*/
public class KernelUidCpuFreqTimeReader {
private static final boolean DEBUG = false;
private static final String TAG = "KernelUidCpuFreqTimeReader";
static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
+ // Throttle interval in milliseconds
+ private static final long DEFAULT_THROTTLE_INTERVAL = 10_000L;
public interface Callback {
void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs);
}
private long[] mCpuFreqs;
+ private long[] mCurTimes; // Reuse to prevent GC.
+ private long[] mDeltaTimes; // Reuse to prevent GC.
+ private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL;
private int mCpuFreqsCount;
- private long mLastTimeReadMs;
+ private long mLastTimeReadMs = Long.MIN_VALUE;
private long mNowTimeMs;
+ private boolean mReadBinary = true;
+ private final KernelCpuProcReader mProcReader;
private SparseArray<long[]> mLastUidCpuFreqTimeMs = new SparseArray<>();
private boolean mPerClusterTimesAvailable;
private boolean mAllUidTimesAvailable = true;
+ public KernelUidCpuFreqTimeReader() {
+ mProcReader = KernelCpuProcReader.getFreqTimeReaderInstance();
+ }
+
+ @VisibleForTesting
+ public KernelUidCpuFreqTimeReader(KernelCpuProcReader procReader) {
+ mProcReader = procReader;
+ }
+
public boolean perClusterTimesAvailable() {
return mPerClusterTimesAvailable;
}
public long[] readFreqs(@NonNull PowerProfile powerProfile) {
checkNotNull(powerProfile);
-
if (mCpuFreqs != null) {
// No need to read cpu freqs more than once.
return mCpuFreqs;
return readCpuFreqs(line, powerProfile);
}
+ public void setReadBinary(boolean readBinary) {
+ mReadBinary = readBinary;
+ }
+
+ public void setThrottleInterval(long throttleInterval) {
+ if (throttleInterval >= 0) {
+ mThrottleInterval = throttleInterval;
+ }
+ }
+
public void readDelta(@Nullable Callback callback) {
if (mCpuFreqs == null) {
return;
}
+ if (SystemClock.elapsedRealtime() < mLastTimeReadMs + mThrottleInterval) {
+ Slog.w(TAG, "Throttle");
+ return;
+ }
+ mNowTimeMs = SystemClock.elapsedRealtime();
+ if (mReadBinary) {
+ readDeltaBinary(callback);
+ } else {
+ readDeltaString(callback);
+ }
+ mLastTimeReadMs = mNowTimeMs;
+ }
+
+ private void readDeltaString(@Nullable Callback callback) {
final int oldMask = StrictMode.allowThreadDiskReadsMask();
try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
- mNowTimeMs = SystemClock.elapsedRealtime();
readDelta(reader, callback);
- mLastTimeReadMs = mNowTimeMs;
} catch (IOException e) {
Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
} finally {
}
}
+ @VisibleForTesting
+ public void readDeltaBinary(@Nullable Callback callback) {
+ synchronized (mProcReader) {
+ ByteBuffer bytes = mProcReader.readBytes();
+ if (bytes == null || bytes.remaining() <= 4) {
+ // Error already logged in mProcReader.
+ return;
+ }
+ if ((bytes.remaining() & 3) != 0) {
+ Slog.wtf(TAG, "Cannot parse cluster time proc bytes to int: " + bytes.remaining());
+ return;
+ }
+ IntBuffer buf = bytes.asIntBuffer();
+ final int freqs = buf.get();
+ if (freqs != mCpuFreqsCount) {
+ Slog.wtf(TAG, "Cpu freqs expect " + mCpuFreqsCount + " , got " + freqs);
+ return;
+ }
+ if (buf.remaining() % (freqs + 1) != 0) {
+ Slog.wtf(TAG, "Freq time format error: " + buf.remaining() + " / " + (freqs + 1));
+ return;
+ }
+ int numUids = buf.remaining() / (freqs + 1);
+ for (int i = 0; i < numUids; i++) {
+ int uid = buf.get();
+ long[] lastTimes = mLastUidCpuFreqTimeMs.get(uid);
+ if (lastTimes == null) {
+ lastTimes = new long[mCpuFreqsCount];
+ mLastUidCpuFreqTimeMs.put(uid, lastTimes);
+ }
+ boolean notify = false;
+ boolean corrupted = false;
+ for (int j = 0; j < freqs; j++) {
+ mCurTimes[j] = (long) buf.get() * 10; // Unit is 10ms.
+ mDeltaTimes[j] = mCurTimes[j] - lastTimes[j];
+ if (mCurTimes[j] < 0 || mDeltaTimes[j] < 0) {
+ Slog.e(TAG, "Unexpected data from freq time proc: " + mCurTimes[j]);
+ corrupted = true;
+ }
+ notify |= mDeltaTimes[j] > 0;
+ }
+ if (notify && !corrupted) {
+ System.arraycopy(mCurTimes, 0, lastTimes, 0, freqs);
+ if (callback != null) {
+ callback.onUidCpuFreqTime(uid, mDeltaTimes);
+ }
+ }
+ }
+ // Slog.i(TAG, "Read uids: "+numUids);
+ }
+ }
+
public void removeUid(int uid) {
mLastUidCpuFreqTimeMs.delete(uid);
}
// First item would be "uid: " which needs to be ignored.
mCpuFreqsCount = freqStr.length - 1;
mCpuFreqs = new long[mCpuFreqsCount];
+ mCurTimes = new long[mCpuFreqsCount];
+ mDeltaTimes = new long[mCpuFreqsCount];
for (int i = 0; i < mCpuFreqsCount; ++i) {
mCpuFreqs[i] = Long.parseLong(freqStr[i + 1], 10);
}
BatteryStatsTimerTest.class,
BatteryStatsUidTest.class,
BatteryStatsUserLifecycleTests.class,
+ KernelCpuProcReaderTest.class,
KernelMemoryBandwidthStatsTest.class,
KernelSingleUidTimeReaderTest.class,
KernelUidCpuFreqTimeReaderTest.class,
--- /dev/null
+/*
+ * 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.assertNotNull;
+import static org.junit.Assert.assertNull;
+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 org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.Random;
+
+/**
+ * Test class for {@link KernelCpuProcReader}.
+ *
+ * $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuProcReader
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KernelCpuProcReaderTest {
+
+ private File mRoot;
+ private File mTestDir;
+ private File mTestFile;
+ private Random mRand = new Random();
+
+ private KernelCpuProcReader mKernelCpuProcReader;
+
+ private Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+
+ @Before
+ public void setUp() {
+ mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
+ mRoot = getContext().getFilesDir();
+ mTestFile = new File(mTestDir, "test.file");
+ mKernelCpuProcReader = new KernelCpuProcReader(mTestFile.getAbsolutePath());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ FileUtils.deleteContents(mTestDir);
+ FileUtils.deleteContents(mRoot);
+ }
+
+
+ /**
+ * Tests that reading will return null if the file does not exist.
+ */
+ @Test
+ public void testReadInvalidFile() throws Exception {
+ assertEquals(null, mKernelCpuProcReader.readBytes());
+ }
+
+ /**
+ * Tests that reading will always return null after 5 failures.
+ */
+ @Test
+ public void testReadErrorsLimit() throws Exception {
+ mKernelCpuProcReader.setThrottleInterval(0);
+ for (int i = 0; i < 3; i++) {
+ assertNull(mKernelCpuProcReader.readBytes());
+ SystemClock.sleep(50);
+ }
+
+ final byte[] data = new byte[1024];
+ mRand.nextBytes(data);
+ try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
+ os.write(data);
+ }
+ assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
+
+ assertTrue(mTestFile.delete());
+ for (int i = 0; i < 3; i++) {
+ assertNull(mKernelCpuProcReader.readBytes());
+ SystemClock.sleep(50);
+ }
+ try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
+ os.write(data);
+ }
+ assertNull(mKernelCpuProcReader.readBytes());
+ }
+
+ /**
+ * Tests reading functionality.
+ */
+ @Test
+ public void testSimpleRead() throws Exception {
+ final byte[] data = new byte[1024];
+ mRand.nextBytes(data);
+ try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
+ os.write(data);
+ }
+ assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
+ }
+
+ /**
+ * Tests multiple reading functionality.
+ */
+ @Test
+ public void testMultipleRead() throws Exception {
+ mKernelCpuProcReader.setThrottleInterval(0);
+ for (int i = 0; i < 100; i++) {
+ final byte[] data = new byte[mRand.nextInt(102400) + 4];
+ mRand.nextBytes(data);
+ try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
+ os.write(data);
+ }
+ assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
+ assertTrue(mTestFile.delete());
+ }
+ }
+
+ /**
+ * Tests reading with resizing.
+ */
+ @Test
+ public void testReadWithResize() throws Exception {
+ final byte[] data = new byte[128001];
+ mRand.nextBytes(data);
+ try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
+ os.write(data);
+ }
+ assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
+ }
+
+ /**
+ * Tests that reading a file over the limit (1MB) will return null.
+ */
+ @Test
+ public void testReadOverLimit() throws Exception {
+ final byte[] data = new byte[1228800];
+ mRand.nextBytes(data);
+ try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
+ os.write(data);
+ }
+ assertNull(mKernelCpuProcReader.readBytes());
+ }
+
+ /**
+ * Tests throttling. Deleting underlying file should not affect cache.
+ */
+ @Test
+ public void testThrottle() throws Exception {
+ mKernelCpuProcReader.setThrottleInterval(3000);
+ final byte[] data = new byte[20001];
+ mRand.nextBytes(data);
+ try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
+ os.write(data);
+ }
+ assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
+ assertTrue(mTestFile.delete());
+ for (int i = 0; i < 5; i++) {
+ assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
+ SystemClock.sleep(10);
+ }
+ SystemClock.sleep(5000);
+ assertNull(mKernelCpuProcReader.readBytes());
+ }
+
+ private byte[] toArray(ByteBuffer buffer) {
+ assertNotNull(buffer);
+ byte[] arr = new byte[buffer.remaining()];
+ buffer.get(arr);
+ return arr;
+ }
+}
package com.android.internal.os;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import java.io.BufferedReader;
-import java.util.Arrays;
-import java.util.List;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.util.Random;
/**
*
* To run it:
* bit FrameworksCoreTests:com.android.internal.os.KernelUidCpuActiveTimeReaderTest
- *
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class KernelUidCpuActiveTimeReaderTest {
- @Mock private BufferedReader mBufferedReader;
- @Mock private KernelUidCpuActiveTimeReader.Callback mCallback;
-
+ @Mock
+ private KernelCpuProcReader mProcReader;
+ @Mock
+ private KernelUidCpuActiveTimeReader.Callback mCallback;
private KernelUidCpuActiveTimeReader mReader;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mReader = new KernelUidCpuActiveTimeReader();
- }
-
- public class Temp {
-
- public void method() {
- method1(new long[][]{{1,2,3}, {2,3,4}});
- method1(new long[][]{{2,2,3}, {2,3,4}});
- }
- public int method1(long[][] array) {
- return array.length * array[0].length;
- }
+ mReader = new KernelUidCpuActiveTimeReader(mProcReader);
+ mReader.setThrottleInterval(0);
}
@Test
public void testReadDelta() throws Exception {
final int cores = 8;
- final String info = "active: 8";
final int[] uids = {1, 22, 333, 4444, 5555};
final long[][] times = increaseTime(new long[uids.length][cores]);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- for(int i=0;i<uids.length;i++){
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < uids.length; i++) {
verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(times[i]));
}
verifyNoMoreInteractions(mCallback);
// Verify that a second call will only return deltas.
- Mockito.reset(mCallback, mBufferedReader);
+ Mockito.reset(mCallback);
final long[][] times1 = increaseTime(times);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times1));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- for(int i=0;i<uids.length;i++){
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times1));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < uids.length; i++) {
verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times1[i], times[i])));
}
verifyNoMoreInteractions(mCallback);
// Verify that there won't be a callback if the proc file values didn't change.
- Mockito.reset(mCallback, mBufferedReader);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times1));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
+ Mockito.reset(mCallback);
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times1));
+ mReader.readDelta(mCallback);
verifyNoMoreInteractions(mCallback);
// Verify that calling with a null callback doesn't result in any crashes
- Mockito.reset(mCallback, mBufferedReader);
+ Mockito.reset(mCallback);
final long[][] times2 = increaseTime(times1);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times2));
- mReader.readDeltaInternal(mBufferedReader, null);
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times2));
+ mReader.readDelta(null);
// Verify that the readDelta call will only return deltas when
// the previous call had null callback.
- Mockito.reset(mCallback, mBufferedReader);
+ Mockito.reset(mCallback);
final long[][] times3 = increaseTime(times2);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times3));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times3));
+ mReader.readDelta(mCallback);
for (int i = 0; i < uids.length; ++i) {
verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times3[i], times2[i])));
}
@Test
public void testReadDelta_malformedData() throws Exception {
final int cores = 8;
- final String info = "active: 8";
final int[] uids = {1, 22, 333, 4444, 5555};
final long[][] times = increaseTime(new long[uids.length][cores]);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- for(int i=0;i<uids.length;i++){
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < uids.length; i++) {
verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(times[i]));
}
verifyNoMoreInteractions(mCallback);
- // Verify that there is no callback if subsequent call provides wrong # of entries.
- Mockito.reset(mCallback, mBufferedReader);
- final long[][] temp = increaseTime(times);
- final long[][] times1 = new long[uids.length][];
- for(int i=0;i<temp.length;i++){
- times1[i] = Arrays.copyOfRange(temp[i], 0, 6);
- }
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times1));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
+ // Verify that there is no callback if subsequent call is in wrong format.
+ Mockito.reset(mCallback);
+ final long[][] times1 = increaseTime(times);
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times1).putInt(0, 5));
+ mReader.readDelta(mCallback);
verifyNoMoreInteractions(mCallback);
// Verify that the internal state was not modified if the given core count does not match
// the following # of entries.
- Mockito.reset(mCallback, mBufferedReader);
+ Mockito.reset(mCallback);
final long[][] times2 = increaseTime(times);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times2));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- for(int i=0;i<uids.length;i++){
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times2));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < uids.length; i++) {
verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times2[i], times[i])));
}
verifyNoMoreInteractions(mCallback);
// Verify that there is no callback if any value in the proc file is -ve.
- Mockito.reset(mCallback, mBufferedReader);
+ Mockito.reset(mCallback);
final long[][] times3 = increaseTime(times2);
times3[uids.length - 1][cores - 1] *= -1;
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times3));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times3));
+ mReader.readDelta(mCallback);
for (int i = 0; i < uids.length - 1; ++i) {
verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times3[i], times2[i])));
}
verifyNoMoreInteractions(mCallback);
// Verify that the internal state was not modified when the proc file had -ve value.
- Mockito.reset(mCallback, mBufferedReader);
+ Mockito.reset(mCallback);
for (int i = 0; i < cores; i++) {
- times3[uids.length - 1][i] = times2[uids.length - 1][i] + uids[uids.length - 1] * 1000;
+ times3[uids.length - 1][i] = times2[uids.length - 1][i] + uids[uids.length - 1] * 2520;
}
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times3));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- verify(mCallback).onUidCpuActiveTime(uids[uids.length - 1], getTotal(subtract(times3[uids.length - 1], times2[uids.length - 1])));
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times3));
+ mReader.readDelta(mCallback);
+ verify(mCallback).onUidCpuActiveTime(uids[uids.length - 1],
+ getTotal(subtract(times3[uids.length - 1], times2[uids.length - 1])));
verifyNoMoreInteractions(mCallback);
// Verify that there is no callback if the values in the proc file are decreased.
- Mockito.reset(mCallback, mBufferedReader);
+ Mockito.reset(mCallback);
final long[][] times4 = increaseTime(times3);
- times4[uids.length - 1][cores - 1] = times3[uids.length - 1][cores - 1] - 1;
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times4));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
+ System.arraycopy(times3[uids.length - 1], 0, times4[uids.length - 1], 0, cores);
+ times4[uids.length - 1][cores - 1] -= 100;
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times4));
+ mReader.readDelta(mCallback);
for (int i = 0; i < uids.length - 1; ++i) {
verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times4[i], times3[i])));
}
verifyNoMoreInteractions(mCallback);
// Verify that the internal state was not modified when the proc file had decreased values.
- Mockito.reset(mCallback, mBufferedReader);
+ Mockito.reset(mCallback);
for (int i = 0; i < cores; i++) {
- times4[uids.length - 1][i] = times3[uids.length - 1][i] + uids[uids.length - 1] * 1000;
+ times4[uids.length - 1][i] = times3[uids.length - 1][i] + uids[uids.length - 1] * 2520;
}
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times4));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- verify(mCallback).onUidCpuActiveTime(uids[uids.length - 1], getTotal(subtract(times4[uids.length - 1], times3[uids.length - 1])));
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times4));
+ mReader.readDelta(mCallback);
+ verify(mCallback).onUidCpuActiveTime(uids[uids.length - 1],
+ getTotal(subtract(times4[uids.length - 1], times3[uids.length - 1])));
verifyNoMoreInteractions(mCallback);
}
return val;
}
- private String[] formatTime(int[] uids, long[][] times) {
- String[] lines = new String[uids.length + 1];
- for (int i=0;i<uids.length;i++){
- StringBuilder sb = new StringBuilder();
- sb.append(uids[i]).append(':');
- for(int j=0;j<times[i].length;j++){
- sb.append(' ').append(times[i][j]);
- }
- lines[i] = sb.toString();
- }
- lines[uids.length] = null;
- return lines;
- }
-
+ /**
+ * Unit of original and return value is 10ms. What's special about 2520? 2520 is LCM of 1, 2, 3,
+ * ..., 10. So that when wedivide shared cpu time by concurrent thread count, we always get a
+ * nice integer, avoiding rounding errors.
+ */
private long[][] increaseTime(long[][] original) {
long[][] newTime = new long[original.length][original[0].length];
Random rand = new Random();
- for(int i = 0;i<original.length;i++){
- for(int j=0;j<original[0].length;j++){
- newTime[i][j] = original[i][j] + rand.nextInt(1000_000) + 10000;
+ for (int i = 0; i < original.length; i++) {
+ for (int j = 0; j < original[0].length; j++) {
+ newTime[i][j] = original[i][j] + rand.nextInt(1000) * 2520 + 2520;
}
}
return newTime;
}
+ // Unit of times is 10ms
private long getTotal(long[] times) {
long sum = 0;
- for(int i=0;i<times.length;i++){
- sum+=times[i] * 10 / (i+1);
+ for (int i = 0; i < times.length; i++) {
+ sum += times[i] * 10 / (i + 1);
}
return sum;
}
+
+ /**
+ * Format uids and times (in 10ms) into the following format:
+ * [n, uid0, time0a, time0b, ..., time0n,
+ * uid1, time1a, time1b, ..., time1n,
+ * uid2, time2a, time2b, ..., time2n, etc.]
+ * where n is the total number of cpus (num_possible_cpus)
+ */
+ private ByteBuffer getUidTimesBytes(int[] uids, long[][] times) {
+ int size = (1 + uids.length * (times[0].length + 1)) * 4;
+ ByteBuffer buf = ByteBuffer.allocate(size);
+ buf.order(ByteOrder.nativeOrder());
+ buf.putInt(times[0].length);
+ for (int i = 0; i < uids.length; i++) {
+ buf.putInt(uids[i]);
+ for (int j = 0; j < times[i].length; j++) {
+ buf.putInt((int) times[i][j]);
+ }
+ }
+ buf.flip();
+ return buf.order(ByteOrder.nativeOrder());
+ }
}
package com.android.internal.os;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.when;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
-import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import java.io.BufferedReader;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.util.Arrays;
-import java.util.List;
import java.util.Random;
/**
*
* To run it:
* bit FrameworksCoreTests:com.android.internal.os.KernelUidCpuClusterTimeReaderTest
- *
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class KernelUidCpuClusterTimeReaderTest {
- @Mock private BufferedReader mBufferedReader;
- @Mock private KernelUidCpuClusterTimeReader.Callback mCallback;
-
+ @Mock
+ private KernelCpuProcReader mProcReader;
private KernelUidCpuClusterTimeReader mReader;
+ private VerifiableCallback mCallback;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mReader = new KernelUidCpuClusterTimeReader();
+ mReader = new KernelUidCpuClusterTimeReader(mProcReader);
+ mCallback = new VerifiableCallback();
+ mReader.setThrottleInterval(0);
}
@Test
public void testReadDelta() throws Exception {
- final String info = "policy0: 2 policy4: 4";
+ VerifiableCallback cb = new VerifiableCallback();
final int cores = 6;
- final int[] cluster = {2, 4};
+ final int[] clusters = {2, 4};
final int[] uids = {1, 22, 333, 4444, 5555};
// Verify initial call
final long[][] times = increaseTime(new long[uids.length][cores]);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- for (int i=0;i<uids.length;i++){
- verify(mCallback).onUidCpuPolicyTime(uids[i], getTotal(cluster, times[i]));
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times));
+ mReader.readDelta(cb);
+ for (int i = 0; i < uids.length; i++) {
+ cb.verify(uids[i], getTotal(clusters, times[i]));
}
+ cb.verifyNoMoreInteractions();
// Verify that a second call will only return deltas.
- Mockito.reset(mCallback, mBufferedReader);
+ cb.clear();
+ Mockito.reset(mProcReader);
final long[][] times1 = increaseTime(times);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times1));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- for (int i=0;i<uids.length;i++){
- verify(mCallback).onUidCpuPolicyTime(uids[i], getTotal(cluster, subtract(times1[i], times[i])));
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
+ mReader.readDelta(cb);
+ for (int i = 0; i < uids.length; i++) {
+ cb.verify(uids[i], getTotal(clusters, subtract(times1[i], times[i])));
}
+ cb.verifyNoMoreInteractions();
// Verify that there won't be a callback if the proc file values didn't change.
- Mockito.reset(mCallback, mBufferedReader);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times1));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- verifyNoMoreInteractions(mCallback);
+ cb.clear();
+ Mockito.reset(mProcReader);
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
+ mReader.readDelta(cb);
+ cb.verifyNoMoreInteractions();
// Verify that calling with a null callback doesn't result in any crashes
- Mockito.reset(mCallback, mBufferedReader);
+ Mockito.reset(mProcReader);
final long[][] times2 = increaseTime(times1);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times2));
- mReader.readDeltaInternal(mBufferedReader, null);
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times2));
+ mReader.readDelta(null);
// Verify that the readDelta call will only return deltas when
// the previous call had null callback.
- Mockito.reset(mCallback, mBufferedReader);
+ cb.clear();
+ Mockito.reset(mProcReader);
final long[][] times3 = increaseTime(times2);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times3));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- for (int i=0;i<uids.length;i++){
- verify(mCallback).onUidCpuPolicyTime(uids[i], getTotal(cluster, subtract(times3[i], times2[i])));
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3));
+ mReader.readDelta(cb);
+ for (int i = 0; i < uids.length; i++) {
+ cb.verify(uids[i], getTotal(clusters, subtract(times3[i], times2[i])));
}
+ cb.verifyNoMoreInteractions();
}
@Test
public void testReadDelta_malformedData() throws Exception {
- final String info = "policy0: 2 policy4: 4";
final int cores = 6;
- final int[] cluster = {2, 4};
+ final int[] clusters = {2, 4};
final int[] uids = {1, 22, 333, 4444, 5555};
// Verify initial call
final long[][] times = increaseTime(new long[uids.length][cores]);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- for (int i=0;i<uids.length;i++){
- verify(mCallback).onUidCpuPolicyTime(uids[i], getTotal(cluster, times[i]));
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < uids.length; i++) {
+ mCallback.verify(uids[i], getTotal(clusters, times[i]));
}
+ mCallback.verifyNoMoreInteractions();
- // Verify that there is no callback if subsequent call provides wrong # of entries.
- Mockito.reset(mCallback, mBufferedReader);
+ // Verify that there is no callback if a call has wrong format
+ mCallback.clear();
+ Mockito.reset(mProcReader);
final long[][] temp = increaseTime(times);
final long[][] times1 = new long[uids.length][];
- for(int i=0;i<temp.length;i++){
+ for (int i = 0; i < temp.length; i++) {
times1[i] = Arrays.copyOfRange(temp[i], 0, 4);
}
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times1));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- verifyNoMoreInteractions(mCallback);
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
+ mReader.readDelta(mCallback);
+ mCallback.verifyNoMoreInteractions();
// Verify that the internal state was not modified if the given core count does not match
// the following # of entries.
- Mockito.reset(mCallback, mBufferedReader);
+ mCallback.clear();
+ Mockito.reset(mProcReader);
final long[][] times2 = increaseTime(times);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times2));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- for (int i=0;i<uids.length;i++){
- verify(mCallback).onUidCpuPolicyTime(uids[i], getTotal(cluster, subtract(times2[i], times[i])));
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times2));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < uids.length; i++) {
+ mCallback.verify(uids[i], getTotal(clusters, subtract(times2[i], times[i])));
}
+ mCallback.verifyNoMoreInteractions();
// Verify that there is no callback if any value in the proc file is -ve.
- Mockito.reset(mCallback, mBufferedReader);
+ mCallback.clear();
+ Mockito.reset(mProcReader);
final long[][] times3 = increaseTime(times2);
times3[uids.length - 1][cores - 1] *= -1;
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times3));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- for (int i=0;i<uids.length-1;i++){
- verify(mCallback).onUidCpuPolicyTime(uids[i], getTotal(cluster, subtract(times3[i], times2[i])));
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < uids.length - 1; i++) {
+ mCallback.verify(uids[i], getTotal(clusters, subtract(times3[i], times2[i])));
}
- verifyNoMoreInteractions(mCallback);
+ mCallback.verifyNoMoreInteractions();
// Verify that the internal state was not modified when the proc file had -ve value.
- Mockito.reset(mCallback, mBufferedReader);
- for(int i=0;i<cores;i++){
- times3[uids.length -1][i] = times2[uids.length -1][i] + uids[uids.length -1]*1000;
+ mCallback.clear();
+ Mockito.reset(mProcReader);
+ for (int i = 0; i < cores; i++) {
+ times3[uids.length - 1][i] = times2[uids.length - 1][i] + uids[uids.length - 1] * 2520;
}
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times3));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- verify(mCallback, times(1)).onUidCpuPolicyTime(uids[uids.length-1], getTotal(cluster, subtract(times3[uids.length -1], times2[uids.length -1])));
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3));
+ mReader.readDelta(mCallback);
+ mCallback.verify(uids[uids.length - 1],
+ getTotal(clusters, subtract(times3[uids.length - 1], times2[uids.length - 1])));
- // // Verify that there is no callback if the values in the proc file are decreased.
- Mockito.reset(mCallback, mBufferedReader);
+ // Verify that there is no callback if the values in the proc file are decreased.
+ mCallback.clear();
+ Mockito.reset(mProcReader);
final long[][] times4 = increaseTime(times3);
- times4[uids.length - 1][cores - 1] = times3[uids.length - 1][cores - 1] - 1;
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times4));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- for (int i=0;i<uids.length-1;i++){
- verify(mCallback).onUidCpuPolicyTime(uids[i], getTotal(cluster, subtract(times4[i], times3[i])));
+ System.arraycopy(times3[uids.length - 1], 0, times4[uids.length - 1], 0, cores);
+ times4[uids.length - 1][cores - 1] -= 100;
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times4));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < uids.length - 1; i++) {
+ mCallback.verify(uids[i], getTotal(clusters, subtract(times4[i], times3[i])));
}
- verifyNoMoreInteractions(mCallback);
+ mCallback.verifyNoMoreInteractions();
// Verify that the internal state was not modified when the proc file had decreased values.
- Mockito.reset(mCallback, mBufferedReader);
- for(int i=0;i<cores;i++){
- times4[uids.length -1][i] = times3[uids.length -1][i] + uids[uids.length -1]*1000;
+ mCallback.clear();
+ Mockito.reset(mProcReader);
+ for (int i = 0; i < cores; i++) {
+ times4[uids.length - 1][i] = times3[uids.length - 1][i] + uids[uids.length - 1] * 2520;
}
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times4));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- verify(mCallback, times(1))
- .onUidCpuPolicyTime(uids[uids.length-1], getTotal(cluster, subtract(times3[uids.length -1], times2[uids.length -1])));
-
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times4));
+ mReader.readDelta(mCallback);
+ mCallback.verify(uids[uids.length - 1],
+ getTotal(clusters, subtract(times3[uids.length - 1], times2[uids.length - 1])));
+ mCallback.verifyNoMoreInteractions();
}
return val;
}
- private String[] formatTime(int[] uids, long[][] times) {
- String[] lines = new String[uids.length + 1];
- for (int i=0;i<uids.length;i++){
- StringBuilder sb = new StringBuilder();
- sb.append(uids[i]).append(':');
- for(int j=0;j<times[i].length;j++){
- sb.append(' ').append(times[i][j]);
- }
- lines[i] = sb.toString();
- }
- lines[uids.length] = null;
- return lines;
- }
-
+ /**
+ * Unit is 10ms. What's special about 2520? 2520 is LCM of 1, 2, 3, ..., 10. So that when we
+ * divide shared cpu time by concurrent thread count, we always get a nice integer, avoiding
+ * rounding errors.
+ */
private long[][] increaseTime(long[][] original) {
long[][] newTime = new long[original.length][original[0].length];
Random rand = new Random();
- for(int i = 0;i<original.length;i++){
- for(int j=0;j<original[0].length;j++){
- newTime[i][j] = original[i][j] + rand.nextInt(1000_000) + 10000;
+ for (int i = 0; i < original.length; i++) {
+ for (int j = 0; j < original[0].length; j++) {
+ newTime[i][j] = original[i][j] + rand.nextInt(1000) * 2520 + 2520;
}
}
return newTime;
// Format an array of cluster times according to the algorithm in KernelUidCpuClusterTimeReader
private long[] getTotal(int[] cluster, long[] times) {
int core = 0;
- long[] sum = new long[cluster.length];
- for(int i=0;i<cluster.length;i++){
- for(int j=0;j<cluster[i];j++){
- sum[i] += times[core++] * 10 / (j+1);
+ long[] sumTimes = new long[cluster.length];
+ for (int i = 0; i < cluster.length; i++) {
+ double sum = 0;
+ for (int j = 0; j < cluster[i]; j++) {
+ sum += (double) times[core++] * 10 / (j + 1);
}
+ sumTimes[i] = (long) sum;
}
- return sum;
+ return sumTimes;
}
- // Compare array1 against flattened 2d array array2 element by element
- private boolean testEqual(long[] array1, long[][] array2) {
- int k=0;
- for(int i=0;i<array2.length;i++){
- for(int j=0;j<array2[i].length;j++){
- if (k >= array1.length || array1[k++]!=array2[i][j])return false;
+ private class VerifiableCallback implements KernelUidCpuClusterTimeReader.Callback {
+
+ SparseArray<long[]> mData = new SparseArray<>();
+ int count = 0;
+
+ public void verify(int uid, long[] cpuClusterTimeMs) {
+ long[] array = mData.get(uid);
+ assertNotNull(array);
+ assertArrayEquals(cpuClusterTimeMs, array);
+ count++;
+ }
+
+ public void clear() {
+ mData.clear();
+ count = 0;
+ }
+
+ @Override
+ public void onUidCpuPolicyTime(int uid, long[] cpuClusterTimeMs) {
+ long[] array = new long[cpuClusterTimeMs.length];
+ System.arraycopy(cpuClusterTimeMs, 0, array, 0, array.length);
+ mData.put(uid, array);
+ }
+
+ public void verifyNoMoreInteractions() {
+ assertEquals(mData.size(), count);
+ }
+ }
+
+ /**
+ * Format uids and times (in 10ms) into the following format:
+ * [n, x0, ..., xn, uid0, time0a, time0b, ..., time0n,
+ * uid1, time1a, time1b, ..., time1n,
+ * uid2, time2a, time2b, ..., time2n, etc.]
+ * where n is the number of policies
+ * xi is the number cpus on a particular policy
+ */
+ private ByteBuffer getUidTimesBytes(int[] uids, int[] clusters, long[][] times) {
+ int size = (1 + clusters.length + uids.length * (times[0].length + 1)) * 4;
+ ByteBuffer buf = ByteBuffer.allocate(size);
+ buf.order(ByteOrder.nativeOrder());
+ buf.putInt(clusters.length);
+ for (int i = 0; i < clusters.length; i++) {
+ buf.putInt(clusters[i]);
+ }
+ for (int i = 0; i < uids.length; i++) {
+ buf.putInt(uids[i]);
+ for (int j = 0; j < times[i].length; j++) {
+ buf.putInt((int) (times[i][j]));
}
}
- return k == array1.length;
+ buf.flip();
+ return buf.order(ByteOrder.nativeOrder());
}
}
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.assertNotNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
import org.junit.Before;
import org.junit.Test;
import org.mockito.MockitoAnnotations;
import java.io.BufferedReader;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.util.Arrays;
/**
*
* Build: m FrameworksCoreTests
* Install: adb install -r \
- * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk
+ * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk
* Run: adb shell am instrument -e class com.android.internal.os.KernelUidCpuFreqTimeReaderTest -w \
- * com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
+ * com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
*
* or
*
@SmallTest
@RunWith(AndroidJUnit4.class)
public class KernelUidCpuFreqTimeReaderTest {
- @Mock private BufferedReader mBufferedReader;
- @Mock private KernelUidCpuFreqTimeReader.Callback mCallback;
- @Mock private PowerProfile mPowerProfile;
+ @Mock
+ private BufferedReader mBufferedReader;
+ @Mock
+ private KernelUidCpuFreqTimeReader.Callback mCallback;
+ @Mock
+ private PowerProfile mPowerProfile;
+ @Mock
+ private KernelCpuProcReader mProcReader;
private KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mKernelUidCpuFreqTimeReader = new KernelUidCpuFreqTimeReader();
+ mKernelUidCpuFreqTimeReader = new KernelUidCpuFreqTimeReader(mProcReader);
+ mKernelUidCpuFreqTimeReader.setThrottleInterval(0);
}
@Test
.thenReturn(getFreqsLine(freqs), getUidTimesLines(uids, times));
mKernelUidCpuFreqTimeReader.readDelta(mBufferedReader, mCallback);
for (int i = 0; i < uids.length; ++i) {
- verify(mCallback).onUidCpuFreqTime(uids[i], times[i]);
+ Mockito.verify(mCallback).onUidCpuFreqTime(uids[i], times[i]);
}
verifyNoMoreInteractions(mCallback);
.thenReturn(getFreqsLine(freqs), getUidTimesLines(uids, newTimes1));
mKernelUidCpuFreqTimeReader.readDelta(mBufferedReader, mCallback);
for (int i = 0; i < uids.length; ++i) {
- verify(mCallback).onUidCpuFreqTime(uids[i], subtract(newTimes1[i], times[i]));
+ Mockito.verify(mCallback).onUidCpuFreqTime(uids[i], subtract(newTimes1[i], times[i]));
}
verifyNoMoreInteractions(mCallback);
.thenReturn(getFreqsLine(freqs), getUidTimesLines(uids, newTimes3));
mKernelUidCpuFreqTimeReader.readDelta(mBufferedReader, mCallback);
for (int i = 0; i < uids.length; ++i) {
- verify(mCallback).onUidCpuFreqTime(uids[i], subtract(newTimes3[i], newTimes2[i]));
+ Mockito.verify(mCallback).onUidCpuFreqTime(uids[i],
+ subtract(newTimes3[i], newTimes2[i]));
}
verifyNoMoreInteractions(mCallback);
}
@Test
+ public void testReadDelta_Binary() throws Exception {
+ VerifiableCallback cb = new VerifiableCallback();
+ final long[] freqs = {110, 123, 145, 167, 289, 997};
+ final int[] uids = {1, 22, 333, 444, 555};
+ final long[][] times = new long[uids.length][freqs.length];
+ for (int i = 0; i < uids.length; ++i) {
+ for (int j = 0; j < freqs.length; ++j) {
+ times[i][j] = uids[i] * freqs[j] * 10;
+ }
+ }
+ when(mBufferedReader.readLine()).thenReturn(getFreqsLine(freqs));
+ long[] actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mBufferedReader, mPowerProfile);
+
+ assertArrayEquals(freqs, actualFreqs);
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times));
+ mKernelUidCpuFreqTimeReader.readDeltaBinary(cb);
+ for (int i = 0; i < uids.length; ++i) {
+ cb.verify(uids[i], times[i]);
+ }
+ cb.verifyNoMoreInteractions();
+
+ // Verify that a second call will only return deltas.
+ cb.clear();
+ Mockito.reset(mProcReader);
+ final long[][] newTimes1 = new long[uids.length][freqs.length];
+ for (int i = 0; i < uids.length; ++i) {
+ for (int j = 0; j < freqs.length; ++j) {
+ newTimes1[i][j] = times[i][j] + (uids[i] + freqs[j]) * 50;
+ }
+ }
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes1));
+ mKernelUidCpuFreqTimeReader.readDeltaBinary(cb);
+ for (int i = 0; i < uids.length; ++i) {
+ cb.verify(uids[i], subtract(newTimes1[i], times[i]));
+ }
+ cb.verifyNoMoreInteractions();
+
+ // Verify that there won't be a callback if the proc file values didn't change.
+ cb.clear();
+ Mockito.reset(mProcReader);
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes1));
+ mKernelUidCpuFreqTimeReader.readDeltaBinary(cb);
+ cb.verifyNoMoreInteractions();
+
+ // Verify that calling with a null callback doesn't result in any crashes
+ cb.clear();
+ Mockito.reset(mProcReader);
+ final long[][] newTimes2 = new long[uids.length][freqs.length];
+ for (int i = 0; i < uids.length; ++i) {
+ for (int j = 0; j < freqs.length; ++j) {
+ newTimes2[i][j] = newTimes1[i][j] + (uids[i] * freqs[j]) * 30;
+ }
+ }
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes2));
+ mKernelUidCpuFreqTimeReader.readDeltaBinary(null);
+ cb.verifyNoMoreInteractions();
+
+ // Verify that the readDelta call will only return deltas when
+ // the previous call had null callback.
+ cb.clear();
+ Mockito.reset(mProcReader);
+ final long[][] newTimes3 = new long[uids.length][freqs.length];
+ for (int i = 0; i < uids.length; ++i) {
+ for (int j = 0; j < freqs.length; ++j) {
+ newTimes3[i][j] = newTimes2[i][j] + (uids[i] + freqs[j]) * 40;
+ }
+ }
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes3));
+ mKernelUidCpuFreqTimeReader.readDeltaBinary(cb);
+ for (int i = 0; i < uids.length; ++i) {
+ cb.verify(uids[i], subtract(newTimes3[i], newTimes2[i]));
+ }
+ cb.verifyNoMoreInteractions();
+ }
+
+ @Test
public void testReadDelta_malformedData() throws Exception {
final long[] freqs = {1, 12, 123, 1234, 12345, 123456};
final int[] uids = {1, 22, 333, 4444, 5555};
.thenReturn(getFreqsLine(freqs), getUidTimesLines(uids, times));
mKernelUidCpuFreqTimeReader.readDelta(mBufferedReader, mCallback);
for (int i = 0; i < uids.length; ++i) {
- verify(mCallback).onUidCpuFreqTime(uids[i], times[i]);
+ Mockito.verify(mCallback).onUidCpuFreqTime(uids[i], times[i]);
}
verifyNoMoreInteractions(mCallback);
if (i == uids.length - 1) {
continue;
}
- verify(mCallback).onUidCpuFreqTime(uids[i], subtract(newTimes1[i], times[i]));
+ Mockito.verify(mCallback).onUidCpuFreqTime(uids[i], subtract(newTimes1[i], times[i]));
}
verifyNoMoreInteractions(mCallback);
if (i == uids.length - 1) {
continue;
}
- verify(mCallback).onUidCpuFreqTime(uids[i], subtract(newTimes2[i], newTimes1[i]));
+ Mockito.verify(mCallback).onUidCpuFreqTime(uids[i],
+ subtract(newTimes2[i], newTimes1[i]));
}
verifyNoMoreInteractions(mCallback);
return lines;
}
+ private ByteBuffer getUidTimesBytes(int[] uids, long[][] times) {
+ int size = (1 + uids.length + uids.length * times[0].length) * 4;
+ ByteBuffer buf = ByteBuffer.allocate(size);
+ buf.order(ByteOrder.nativeOrder());
+ buf.putInt(times[0].length);
+ for (int i = 0; i < uids.length; i++) {
+ buf.putInt(uids[i]);
+ for (int j = 0; j < times[i].length; j++) {
+ buf.putInt((int) (times[i][j] / 10));
+ }
+ }
+ buf.flip();
+ return buf.asReadOnlyBuffer().order(ByteOrder.nativeOrder());
+ }
+
private void setCpuClusterFreqs(int numClusters, int... clusterFreqs) {
assertEquals(numClusters, clusterFreqs.length);
when(mPowerProfile.getNumCpuClusters()).thenReturn(numClusters);
when(mPowerProfile.getNumSpeedStepsInCpuCluster(i)).thenReturn(clusterFreqs[i]);
}
}
+
+ private class VerifiableCallback implements KernelUidCpuFreqTimeReader.Callback {
+
+ SparseArray<long[]> mData = new SparseArray<>();
+ int count = 0;
+
+ public void verify(int uid, long[] cpuFreqTimeMs) {
+ long[] array = mData.get(uid);
+ assertNotNull(array);
+ assertArrayEquals(cpuFreqTimeMs, array);
+ count++;
+ }
+
+ public void clear() {
+ mData.clear();
+ count = 0;
+ }
+
+ @Override
+ public void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs) {
+ long[] array = new long[cpuFreqTimeMs.length];
+ System.arraycopy(cpuFreqTimeMs, 0, array, 0, array.length);
+ mData.put(uid, array);
+ }
+
+ public void verifyNoMoreInteractions() {
+ assertEquals(mData.size(), count);
+ }
+ }
}