From: Mike Ma Date: Tue, 13 Feb 2018 22:22:47 +0000 (-0800) Subject: Binary Cpu Time Proc File Reader X-Git-Tag: android-x86-9.0-r1~201^2~10^2 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=2ab014426647bbc8960fdb4dadfe480b9806676e;p=android-x86%2Fframeworks-base.git Binary Cpu Time Proc File Reader Kernel now exposes cpu time proc files in binary format to save cpu cycles. New proc files are: Per uid freq time: /proc/uid_cpupower/time_in_state Per uid active time: /proc/uid_cpupower/concurrent_active_time Per uid cluster time: /proc/uid_cpupower/concurrent_policy_time (See PowerProfile.java on how these data are used) KernelUidCpuActiveTimeReader, KernelUidCpuClusterTimeReader and KernelUidCpuFreqTimeReader are modified to use these new data. Meanwhile, statsd needs these data too. So the actual reading fuctionality is delegated to singletons in KernelCpuProcReader to accomodate concurrent access of these proc files by more than one party. When multiple read requests hit the singleton within the throttle interval, the singleton will return stale data. Optimization KernelCpuProcReader reuses the same ByteBuffer to save results. KernelUid***Reader all reuse the same long[] array to send results back to BatteryStatsImpl. Estimated saving of ~200 KB GC effort per read request. Add a flag to control throttle interval: kernel_uid_readers_throttle_time Bug: 72763654 Bug: 71906435 Bug: 71905885 Bug: 70517018 Fixes: 73166477 Fixes: 72172569 Test: BatteryStatsCpuTimesTest Test: KernelUidCpuActiveTimeReaderTest Test: KernelUidCpuClusterTimeReaderTest Test: KernelUidCpuFreqTimeReaderTest Test: KernelCpuProcReaderTest Change-Id: I1012667ce3b9eb35e37882a058bd4bceccabbbe9 --- diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 4e515918a0cd..39f7649d0960 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -110,7 +110,10 @@ import java.util.Calendar; 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; @@ -12194,7 +12197,7 @@ public class BatteryStatsImpl extends BatteryStats { @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); @@ -12207,7 +12210,7 @@ public class BatteryStatsImpl extends BatteryStats { return; } final Uid u = getUidStatsLocked(uid); - u.mCpuActiveTimeMs.addCountLocked(cpuActiveTimesUs, onBattery); + u.mCpuActiveTimeMs.addCountLocked(cpuActiveTimesMs, onBattery); }); final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs; @@ -12223,7 +12226,7 @@ public class BatteryStatsImpl extends BatteryStats { @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); @@ -12236,7 +12239,7 @@ public class BatteryStatsImpl extends BatteryStats { return; } final Uid u = getUidStatsLocked(uid); - u.mCpuClusterTimesMs.addCountLocked(cpuClusterTimesUs, onBattery); + u.mCpuClusterTimesMs.addCountLocked(cpuClusterTimesMs, onBattery); }); final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs; @@ -13186,17 +13189,20 @@ public class BatteryStatsImpl extends BatteryStats { = "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(','); @@ -13234,11 +13240,14 @@ public class BatteryStatsImpl extends BatteryStats { 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)); } } @@ -13254,6 +13263,13 @@ public class BatteryStatsImpl extends BatteryStats { } } + 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) { @@ -13263,6 +13279,16 @@ public class BatteryStatsImpl extends BatteryStats { } } + 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); @@ -13272,6 +13298,8 @@ public class BatteryStatsImpl extends BatteryStats { 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); } } diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java index a34e7f50c9c9..101c321ab2c4 100644 --- a/core/java/com/android/internal/os/CpuPowerCalculator.java +++ b/core/java/com/android/internal/os/CpuPowerCalculator.java @@ -50,13 +50,14 @@ public class CpuPowerCalculator extends PowerCalculator { 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=" diff --git a/core/java/com/android/internal/os/KernelCpuProcReader.java b/core/java/com/android/internal/os/KernelCpuProcReader.java new file mode 100644 index 000000000000..4d569053979b --- /dev/null +++ b/core/java/com/android/internal/os/KernelCpuProcReader.java @@ -0,0 +1,159 @@ +/* + * 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; + } +} diff --git a/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java index cb96c5cdfb60..2519412f3246 100644 --- a/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java +++ b/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java @@ -17,53 +17,121 @@ 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 mLastUidCpuActiveTimeMs = new SparseArray<>(); + private final KernelCpuProcReader mProcReader; + private long mLastTimeReadMs = Long.MIN_VALUE; + private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL; + private SparseArray 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; } } @@ -82,65 +150,4 @@ public class KernelUidCpuActiveTimeReader { 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); - } - } - } } diff --git a/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java index 85153bc45d07..41ef8f05f0d1 100644 --- a/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java +++ b/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java @@ -17,162 +17,206 @@ 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 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 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 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 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); + } } diff --git a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java index d97538c30498..a21a70e1d2c9 100644 --- a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java +++ b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java @@ -32,6 +32,8 @@ 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_time_in_state which has the format: @@ -41,24 +43,45 @@ import java.io.IOException; * [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 mLastUidCpuFreqTimeMs = new SparseArray<>(); @@ -69,6 +92,15 @@ public class KernelUidCpuFreqTimeReader { private boolean mPerClusterTimesAvailable; private boolean mAllUidTimesAvailable = true; + public KernelUidCpuFreqTimeReader() { + mProcReader = KernelCpuProcReader.getFreqTimeReaderInstance(); + } + + @VisibleForTesting + public KernelUidCpuFreqTimeReader(KernelCpuProcReader procReader) { + mProcReader = procReader; + } + public boolean perClusterTimesAvailable() { return mPerClusterTimesAvailable; } @@ -83,7 +115,6 @@ public class KernelUidCpuFreqTimeReader { public long[] readFreqs(@NonNull PowerProfile powerProfile) { checkNotNull(powerProfile); - if (mCpuFreqs != null) { // No need to read cpu freqs more than once. return mCpuFreqs; @@ -115,15 +146,37 @@ public class KernelUidCpuFreqTimeReader { 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 { @@ -131,6 +184,58 @@ public class KernelUidCpuFreqTimeReader { } } + @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); } @@ -212,6 +317,8 @@ public class KernelUidCpuFreqTimeReader { // 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); } 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 702f4b8c0dc5..98b7a3fde56a 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java @@ -36,6 +36,7 @@ import org.junit.runners.Suite; BatteryStatsTimerTest.class, BatteryStatsUidTest.class, BatteryStatsUserLifecycleTests.class, + KernelCpuProcReaderTest.class, KernelMemoryBandwidthStatsTest.class, KernelSingleUidTimeReaderTest.class, KernelUidCpuFreqTimeReaderTest.class, diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java new file mode 100644 index 000000000000..efdd7e978853 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java @@ -0,0 +1,198 @@ +/* + * 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; + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuActiveTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuActiveTimeReaderTest.java index 1ac82bd1dc96..312af16cc8a6 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuActiveTimeReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuActiveTimeReaderTest.java @@ -16,8 +16,6 @@ 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; @@ -25,18 +23,15 @@ 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; /** @@ -44,75 +39,64 @@ 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= array1.length || array1[k++]!=array2[i][j])return false; + private class VerifiableCallback implements KernelUidCpuClusterTimeReader.Callback { + + SparseArray 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()); } } diff --git a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java index 95b0b294f1b3..09507218d01a 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java @@ -19,15 +19,15 @@ package com.android.internal.os; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; +import static org.junit.Assert.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; @@ -37,6 +37,8 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.io.BufferedReader; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.Arrays; /** @@ -50,9 +52,9 @@ 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 * @@ -61,16 +63,22 @@ import java.util.Arrays; @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 @@ -154,7 +162,7 @@ public class KernelUidCpuFreqTimeReaderTest { .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); @@ -170,7 +178,7 @@ public class KernelUidCpuFreqTimeReaderTest { .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); @@ -206,12 +214,89 @@ public class KernelUidCpuFreqTimeReaderTest { .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}; @@ -229,7 +314,7 @@ public class KernelUidCpuFreqTimeReaderTest { .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); @@ -249,7 +334,7 @@ public class KernelUidCpuFreqTimeReaderTest { 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); @@ -280,7 +365,8 @@ public class KernelUidCpuFreqTimeReaderTest { 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); @@ -327,6 +413,21 @@ public class KernelUidCpuFreqTimeReaderTest { 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); @@ -334,4 +435,33 @@ public class KernelUidCpuFreqTimeReaderTest { when(mPowerProfile.getNumSpeedStepsInCpuCluster(i)).thenReturn(clusterFreqs[i]); } } + + private class VerifiableCallback implements KernelUidCpuFreqTimeReader.Callback { + + SparseArray 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); + } + } }