OSDN Git Service

Binary Cpu Time Proc File Reader
authorMike Ma <yanmin@google.com>
Tue, 13 Feb 2018 22:22:47 +0000 (14:22 -0800)
committerMike Ma <yanmin@google.com>
Wed, 21 Feb 2018 23:35:34 +0000 (15:35 -0800)
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

core/java/com/android/internal/os/BatteryStatsImpl.java
core/java/com/android/internal/os/CpuPowerCalculator.java
core/java/com/android/internal/os/KernelCpuProcReader.java [new file with mode: 0644]
core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java
core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java
core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java [new file with mode: 0644]
core/tests/coretests/src/com/android/internal/os/KernelUidCpuActiveTimeReaderTest.java
core/tests/coretests/src/com/android/internal/os/KernelUidCpuClusterTimeReaderTest.java
core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java

index 4e51591..39f7649 100644 (file)
@@ -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);
         }
     }
 
index a34e7f5..101c321 100644 (file)
@@ -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 (file)
index 0000000..4d56905
--- /dev/null
@@ -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;
+    }
+}
index cb96c5c..2519412 100644 (file)
 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;
         }
     }
 
@@ -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);
-            }
-        }
-    }
 }
index 85153bc..41ef8f0 100644 (file)
 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);
+    }
 }
index d97538c..a21a70e 100644 (file)
@@ -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<long[]> 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);
         }
index 702f4b8..98b7a3f 100644 (file)
@@ -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 (file)
index 0000000..efdd7e9
--- /dev/null
@@ -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;
+    }
+}
index 1ac82bd..312af16 100644 (file)
@@ -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<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])));
         }
@@ -122,78 +106,76 @@ public class KernelUidCpuActiveTimeReaderTest {
     @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);
     }
 
@@ -205,36 +187,50 @@ public class KernelUidCpuActiveTimeReaderTest {
         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());
+    }
 }
index 0d1f852..d21f541 100644 (file)
 
 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;
 
 /**
@@ -43,147 +42,163 @@ 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();
     }
 
 
@@ -195,26 +210,17 @@ public class KernelUidCpuClusterTimeReaderTest {
         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;
@@ -223,23 +229,69 @@ public class KernelUidCpuClusterTimeReaderTest {
     // 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());
     }
 }
index 95b0b29..0950721 100644 (file)
@@ -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<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);
+        }
+    }
 }