Add diffing to KernelCpuThreadReader

We want to not use WestWorld's diffing so that we can apply thresholds
on the diffs. This is further explained in the KernelCpuThreadReaderDiff
documentation.

Test: atest KernelCpuThreadReaderDiffTest
Bug: 129387487

Change-Id: I638ae92a28236830444dc80e470114a0e23d79a4
diff --git a/apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java b/apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java
index 1f26188..da9ed6e 100644
--- a/apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java
@@ -40,7 +40,7 @@
     public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
     private final KernelCpuThreadReader mKernelCpuThreadReader =
-            KernelCpuThreadReader.create(8, uid -> 1000 <= uid && uid < 2000, 0);
+            KernelCpuThreadReader.create(8, uid -> 1000 <= uid && uid < 2000);
 
     @Test
     public void timeReadCurrentProcessCpuUsage() {
diff --git a/core/java/com/android/internal/os/KernelCpuThreadReader.java b/core/java/com/android/internal/os/KernelCpuThreadReader.java
index e4de158..3686048 100644
--- a/core/java/com/android/internal/os/KernelCpuThreadReader.java
+++ b/core/java/com/android/internal/os/KernelCpuThreadReader.java
@@ -92,24 +92,12 @@
     /** Value returned when there was an error getting an integer ID value (e.g. PID, UID) */
     private static final int ID_ERROR = -1;
 
-    /** Thread ID used when reporting CPU used by other threads */
-    private static final int OTHER_THREADS_ID = -1;
-
-    /** Thread name used when reporting CPU used by other threads */
-    private static final String OTHER_THREADS_NAME = "__OTHER_THREADS";
-
     /**
      * When checking whether to report data for a thread, we check the UID of the thread's owner
      * against this predicate
      */
     private Predicate<Integer> mUidPredicate;
 
-    /**
-     * If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it
-     * will not be reported
-     */
-    private int mMinimumTotalCpuUsageMillis;
-
     /** Where the proc filesystem is mounted */
     private final Path mProcPath;
 
@@ -138,13 +126,11 @@
     public KernelCpuThreadReader(
             int numBuckets,
             Predicate<Integer> uidPredicate,
-            int minimumTotalCpuUsageMillis,
             Path procPath,
             Path initialTimeInStatePath,
             Injector injector)
             throws IOException {
         mUidPredicate = uidPredicate;
-        mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis;
         mProcPath = procPath;
         mProcTimeInStateReader = new ProcTimeInStateReader(initialTimeInStatePath);
         mInjector = injector;
@@ -157,13 +143,11 @@
      * @return the reader, null if an exception was thrown during creation
      */
     @Nullable
-    public static KernelCpuThreadReader create(
-            int numBuckets, Predicate<Integer> uidPredicate, int minimumTotalCpuUsageMillis) {
+    public static KernelCpuThreadReader create(int numBuckets, Predicate<Integer> uidPredicate) {
         try {
             return new KernelCpuThreadReader(
                     numBuckets,
                     uidPredicate,
-                    minimumTotalCpuUsageMillis,
                     DEFAULT_PROC_PATH,
                     DEFAULT_INITIAL_TIME_IN_STATE_PATH,
                     new Injector());
@@ -259,18 +243,6 @@
     }
 
     /**
-     * If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it
-     * will not be reported
-     */
-    void setMinimumTotalCpuUsageMillis(int minimumTotalCpuUsageMillis) {
-        if (minimumTotalCpuUsageMillis < 0) {
-            Slog.w(TAG, "Negative minimumTotalCpuUsageMillis: " + minimumTotalCpuUsageMillis);
-            return;
-        }
-        mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis;
-    }
-
-    /**
      * Read all of the CPU usage statistics for each child thread of a process
      *
      * @param processPath the {@code /proc} path of the thread
@@ -292,7 +264,6 @@
                             + uid);
         }
 
-        int[] filteredThreadsCpuUsage = null;
         final Path allThreadsPath = processPath.resolve("task");
         final ArrayList<ThreadCpuUsage> threadCpuUsages = new ArrayList<>();
         try (DirectoryStream<Path> threadPaths = Files.newDirectoryStream(allThreadsPath)) {
@@ -301,14 +272,6 @@
                 if (threadCpuUsage == null) {
                     continue;
                 }
-                if (mMinimumTotalCpuUsageMillis > totalCpuUsage(threadCpuUsage.usageTimesMillis)) {
-                    if (filteredThreadsCpuUsage == null) {
-                        filteredThreadsCpuUsage = new int[mFrequenciesKhz.length];
-                    }
-                    filteredThreadsCpuUsage =
-                            sumCpuUsage(filteredThreadsCpuUsage, threadCpuUsage.usageTimesMillis);
-                    continue;
-                }
                 threadCpuUsages.add(threadCpuUsage);
             }
         } catch (IOException e) {
@@ -320,14 +283,6 @@
         if (threadCpuUsages.isEmpty()) {
             return null;
         }
-
-        // Add the filtered out thread CPU usage under an "other threads" ThreadCpuUsage
-        if (filteredThreadsCpuUsage != null) {
-            threadCpuUsages.add(
-                    new ThreadCpuUsage(
-                            OTHER_THREADS_ID, OTHER_THREADS_NAME, filteredThreadsCpuUsage));
-        }
-
         if (DEBUG) {
             Slog.d(TAG, "Read CPU usage of " + threadCpuUsages.size() + " threads");
         }
@@ -404,25 +359,6 @@
         }
     }
 
-    /** Get the sum of all CPU usage across all frequencies */
-    @SuppressWarnings("ForLoopReplaceableByForEach")
-    private static int totalCpuUsage(int[] cpuUsage) {
-        int total = 0;
-        for (int i = 0; i < cpuUsage.length; i++) {
-            total += cpuUsage[i];
-        }
-        return total;
-    }
-
-    /** Add two CPU frequency usages together */
-    private static int[] sumCpuUsage(int[] a, int[] b) {
-        int[] summed = new int[a.length];
-        for (int i = 0; i < a.length; i++) {
-            summed[i] = a[i] + b[i];
-        }
-        return summed;
-    }
-
     /** Puts frequencies and usage times into buckets */
     @VisibleForTesting
     public static class FrequencyBucketCreator {
@@ -553,9 +489,10 @@
         public final int processId;
         public final String processName;
         public final int uid;
-        public final ArrayList<ThreadCpuUsage> threadCpuUsages;
+        public ArrayList<ThreadCpuUsage> threadCpuUsages;
 
-        ProcessCpuUsage(
+        @VisibleForTesting
+        public ProcessCpuUsage(
                 int processId,
                 String processName,
                 int uid,
@@ -571,9 +508,10 @@
     public static class ThreadCpuUsage {
         public final int threadId;
         public final String threadName;
-        public final int[] usageTimesMillis;
+        public int[] usageTimesMillis;
 
-        ThreadCpuUsage(int threadId, String threadName, int[] usageTimesMillis) {
+        @VisibleForTesting
+        public ThreadCpuUsage(int threadId, String threadName, int[] usageTimesMillis) {
             this.threadId = threadId;
             this.threadName = threadName;
             this.usageTimesMillis = usageTimesMillis;
diff --git a/core/java/com/android/internal/os/KernelCpuThreadReaderDiff.java b/core/java/com/android/internal/os/KernelCpuThreadReaderDiff.java
new file mode 100644
index 0000000..ffdc33c
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelCpuThreadReaderDiff.java
@@ -0,0 +1,305 @@
+/*
+ * 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.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Delegates per-thread CPU collection to {@link KernelCpuThreadReader}, and calculates the
+ * difference between CPU usage at each call of {@link #getProcessCpuUsageDiffed()}.
+ *
+ * <p>Some notes on the diff calculation:
+ *
+ * <ul>
+ *   <li>The diffing is done between each call of {@link #getProcessCpuUsageDiffed()}, i.e. call N
+ *       of this method will return CPU used by threads between call N-1 and N.
+ *   <li>The first call of {@link #getProcessCpuUsageDiffed()} will return no processes ("first
+ *       call" is the first call in the lifetime of a {@link KernelCpuThreadReaderDiff} object).
+ *   <li>If a thread does not exist at call N, but does exist at call N+1, the diff will assume that
+ *       the CPU usage at call N was zero. Thus, the diff reported will be equivalent to the value
+ *       returned by {@link KernelCpuThreadReader#getProcessCpuUsage()} at call N+1.
+ *   <li>If an error occurs in {@link KernelCpuThreadReader} at call N, we will return no
+ *       information for CPU usage between call N-1 and N (as we don't know the start value) and
+ *       between N and N+1 (as we don't know the end value). Assuming all other calls are
+ *       successful, the next call to return data will be N+2, for the period between N+1 and N+2.
+ *   <li>If an error occurs in this class (but not in {@link KernelCpuThreadReader}) at call N, the
+ *       data will only be dropped for call N, as we can still use the CPU data for the surrounding
+ *       calls.
+ * </ul>
+ *
+ * <p>Additionally to diffing, this class also contains logic for thresholding reported threads. A
+ * thread will not be reported unless its total CPU usage is at least equal to the value set in
+ * {@link #setMinimumTotalCpuUsageMillis}. Filtered thread CPU usage is summed and reported under
+ * one "other threads" thread. This reduces the cardinality of the {@link
+ * #getProcessCpuUsageDiffed()} result.
+ *
+ * <p>Thresholding is done in this class, instead of {@link KernelCpuThreadReader}, and instead of
+ * WestWorld, because the thresholding should be done after diffing, not before. This is because of
+ * two issues with thresholding before diffing:
+ *
+ * <ul>
+ *   <li>We would threshold less and less threads as thread uptime increases.
+ *   <li>We would encounter errors as the filtered threads become unfiltered, as the "other threads"
+ *       result could have negative diffs, and the newly unfiltered threads would have incorrect
+ *       diffs that include CPU usage from when they were filtered.
+ * </ul>
+ *
+ * @hide Only for use within the system server
+ */
+@SuppressWarnings("ForLoopReplaceableByForEach")
+public class KernelCpuThreadReaderDiff {
+    private static final String TAG = "KernelCpuThreadReaderDiff";
+
+    /** Thread ID used when reporting CPU used by other threads */
+    private static final int OTHER_THREADS_ID = -1;
+
+    /** Thread name used when reporting CPU used by other threads */
+    private static final String OTHER_THREADS_NAME = "__OTHER_THREADS";
+
+    private final KernelCpuThreadReader mReader;
+
+    /**
+     * CPU usage from the previous call of {@link #getProcessCpuUsageDiffed()}. Null if there was no
+     * previous call, or if the previous call failed
+     *
+     * <p>Maps the thread's identifier to the per-frequency CPU usage for that thread. The
+     * identifier contains the minimal amount of information to identify a thread (see {@link
+     * ThreadKey} for more information), thus reducing memory consumption.
+     */
+    @Nullable private Map<ThreadKey, int[]> mPreviousCpuUsage;
+
+    /**
+     * If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it
+     * will not be reported
+     */
+    private int mMinimumTotalCpuUsageMillis;
+
+    @VisibleForTesting
+    public KernelCpuThreadReaderDiff(KernelCpuThreadReader reader, int minimumTotalCpuUsageMillis) {
+        mReader = reader;
+        mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis;
+        mPreviousCpuUsage = null;
+    }
+
+    /**
+     * Returns the difference in CPU usage since the last time this method was called.
+     *
+     * @see KernelCpuThreadReader#getProcessCpuUsage()
+     */
+    @Nullable
+    public ArrayList<KernelCpuThreadReader.ProcessCpuUsage> getProcessCpuUsageDiffed() {
+        Map<ThreadKey, int[]> newCpuUsage = null;
+        try {
+            // Get the thread CPU usage and index them by ThreadKey
+            final ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages =
+                    mReader.getProcessCpuUsage();
+            newCpuUsage = createCpuUsageMap(processCpuUsages);
+            // If there is no previous CPU usage, return nothing
+            if (mPreviousCpuUsage == null) {
+                return null;
+            }
+
+            // Do diffing and thresholding for each process
+            for (int i = 0; i < processCpuUsages.size(); i++) {
+                KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = processCpuUsages.get(i);
+                changeToDiffs(mPreviousCpuUsage, processCpuUsage);
+                applyThresholding(processCpuUsage);
+            }
+            return processCpuUsages;
+        } finally {
+            // Always update the previous CPU usage. If we haven't got an update, it will be set to
+            // null, so the next call knows there no previous values
+            mPreviousCpuUsage = newCpuUsage;
+        }
+    }
+
+    /** @see KernelCpuThreadReader#getCpuFrequenciesKhz() */
+    @Nullable
+    public int[] getCpuFrequenciesKhz() {
+        return mReader.getCpuFrequenciesKhz();
+    }
+
+    /**
+     * If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it
+     * will not be reported
+     */
+    void setMinimumTotalCpuUsageMillis(int minimumTotalCpuUsageMillis) {
+        if (minimumTotalCpuUsageMillis < 0) {
+            Slog.w(TAG, "Negative minimumTotalCpuUsageMillis: " + minimumTotalCpuUsageMillis);
+            return;
+        }
+        mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis;
+    }
+
+    /**
+     * Create a map of a thread's identifier to a thread's CPU usage. Used for fast indexing when
+     * calculating diffs
+     */
+    private static Map<ThreadKey, int[]> createCpuUsageMap(
+            List<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages) {
+        final Map<ThreadKey, int[]> cpuUsageMap = new ArrayMap<>();
+        for (int i = 0; i < processCpuUsages.size(); i++) {
+            KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = processCpuUsages.get(i);
+            for (int j = 0; j < processCpuUsage.threadCpuUsages.size(); j++) {
+                KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage =
+                        processCpuUsage.threadCpuUsages.get(j);
+                cpuUsageMap.put(
+                        new ThreadKey(
+                                processCpuUsage.processId,
+                                threadCpuUsage.threadId,
+                                processCpuUsage.processName,
+                                threadCpuUsage.threadName),
+                        threadCpuUsage.usageTimesMillis);
+            }
+        }
+        return cpuUsageMap;
+    }
+
+    /**
+     * Calculate the difference in per-frequency CPU usage for all threads in a process
+     *
+     * @param previousCpuUsage CPU usage from the last call, the base of the diff
+     * @param processCpuUsage CPU usage from the current call, this value is modified to contain the
+     *     diffed values
+     */
+    private static void changeToDiffs(
+            Map<ThreadKey, int[]> previousCpuUsage,
+            KernelCpuThreadReader.ProcessCpuUsage processCpuUsage) {
+        for (int i = 0; i < processCpuUsage.threadCpuUsages.size(); i++) {
+            KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage =
+                    processCpuUsage.threadCpuUsages.get(i);
+            final ThreadKey key =
+                    new ThreadKey(
+                            processCpuUsage.processId,
+                            threadCpuUsage.threadId,
+                            processCpuUsage.processName,
+                            threadCpuUsage.threadName);
+            int[] previous = previousCpuUsage.get(key);
+            if (previous == null) {
+                // If there's no previous CPU usage, assume that it's zero
+                previous = new int[threadCpuUsage.usageTimesMillis.length];
+            }
+            threadCpuUsage.usageTimesMillis =
+                    cpuTimeDiff(threadCpuUsage.usageTimesMillis, previous);
+        }
+    }
+
+    /**
+     * Filter out any threads with less than {@link #mMinimumTotalCpuUsageMillis} total CPU usage
+     *
+     * <p>The sum of the CPU usage of filtered threads is added under a single thread, labeled with
+     * {@link #OTHER_THREADS_ID} and {@link #OTHER_THREADS_NAME}.
+     *
+     * @param processCpuUsage CPU usage to apply thresholding to, this value is modified to change
+     *     the threads it contains
+     */
+    private void applyThresholding(KernelCpuThreadReader.ProcessCpuUsage processCpuUsage) {
+        int[] filteredThreadsCpuUsage = null;
+        final ArrayList<KernelCpuThreadReader.ThreadCpuUsage> thresholded = new ArrayList<>();
+        for (int i = 0; i < processCpuUsage.threadCpuUsages.size(); i++) {
+            KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage =
+                    processCpuUsage.threadCpuUsages.get(i);
+            if (mMinimumTotalCpuUsageMillis > totalCpuUsage(threadCpuUsage.usageTimesMillis)) {
+                if (filteredThreadsCpuUsage == null) {
+                    filteredThreadsCpuUsage = new int[threadCpuUsage.usageTimesMillis.length];
+                }
+                addToCpuUsage(filteredThreadsCpuUsage, threadCpuUsage.usageTimesMillis);
+                continue;
+            }
+            thresholded.add(threadCpuUsage);
+        }
+        if (filteredThreadsCpuUsage != null) {
+            thresholded.add(
+                    new KernelCpuThreadReader.ThreadCpuUsage(
+                            OTHER_THREADS_ID, OTHER_THREADS_NAME, filteredThreadsCpuUsage));
+        }
+        processCpuUsage.threadCpuUsages = thresholded;
+    }
+
+    /** Get the sum of all CPU usage across all frequencies */
+    private static int totalCpuUsage(int[] cpuUsage) {
+        int total = 0;
+        for (int i = 0; i < cpuUsage.length; i++) {
+            total += cpuUsage[i];
+        }
+        return total;
+    }
+
+    /** Add two CPU frequency usages together */
+    private static void addToCpuUsage(int[] a, int[] b) {
+        for (int i = 0; i < a.length; i++) {
+            a[i] += b[i];
+        }
+    }
+
+    /** Subtract two CPU frequency usages from each other */
+    private static int[] cpuTimeDiff(int[] a, int[] b) {
+        int[] difference = new int[a.length];
+        for (int i = 0; i < a.length; i++) {
+            difference[i] = a[i] - b[i];
+        }
+        return difference;
+    }
+
+    /**
+     * Identifies a thread
+     *
+     * <p>Only stores the minimum amount of information to identify a thread. This includes the
+     * PID/TID, but as both are recycled as processes/threads end and begin, we also store the hash
+     * of the name of the process/thread.
+     */
+    private static class ThreadKey {
+        private final int mProcessId;
+        private final int mThreadId;
+        private final int mProcessNameHash;
+        private final int mThreadNameHash;
+
+        ThreadKey(int processId, int threadId, String processName, String threadName) {
+            this.mProcessId = processId;
+            this.mThreadId = threadId;
+            // Only store the hash to reduce memory consumption
+            this.mProcessNameHash = Objects.hash(processName);
+            this.mThreadNameHash = Objects.hash(threadName);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mProcessId, mThreadId, mProcessNameHash, mThreadNameHash);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof ThreadKey)) {
+                return false;
+            }
+            ThreadKey other = (ThreadKey) obj;
+            return mProcessId == other.mProcessId
+                    && mThreadId == other.mThreadId
+                    && mProcessNameHash == other.mProcessNameHash
+                    && mThreadNameHash == other.mThreadNameHash;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/os/KernelCpuThreadReaderSettingsObserver.java b/core/java/com/android/internal/os/KernelCpuThreadReaderSettingsObserver.java
index 3851ce6..f8c0d9e 100644
--- a/core/java/com/android/internal/os/KernelCpuThreadReaderSettingsObserver.java
+++ b/core/java/com/android/internal/os/KernelCpuThreadReaderSettingsObserver.java
@@ -67,12 +67,14 @@
 
     @Nullable private final KernelCpuThreadReader mKernelCpuThreadReader;
 
+    @Nullable private final KernelCpuThreadReaderDiff mKernelCpuThreadReaderDiff;
+
     /**
      * @return returns a created {@link KernelCpuThreadReader} that will be modified by any change
      *     in settings, returns null if creation failed
      */
     @Nullable
-    public static KernelCpuThreadReader getSettingsModifiedReader(Context context) {
+    public static KernelCpuThreadReaderDiff getSettingsModifiedReader(Context context) {
         // Create the observer
         KernelCpuThreadReaderSettingsObserver settingsObserver =
                 new KernelCpuThreadReaderSettingsObserver(context);
@@ -82,7 +84,7 @@
                 .registerContentObserver(
                         settingsUri, false, settingsObserver, UserHandle.USER_SYSTEM);
         // Return the observer's reader
-        return settingsObserver.mKernelCpuThreadReader;
+        return settingsObserver.mKernelCpuThreadReaderDiff;
     }
 
     private KernelCpuThreadReaderSettingsObserver(Context context) {
@@ -90,9 +92,10 @@
         mContext = context;
         mKernelCpuThreadReader =
                 KernelCpuThreadReader.create(
-                        NUM_BUCKETS_DEFAULT,
-                        UidPredicate.fromString(COLLECTED_UIDS_DEFAULT),
-                        MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT);
+                        NUM_BUCKETS_DEFAULT, UidPredicate.fromString(COLLECTED_UIDS_DEFAULT));
+        mKernelCpuThreadReaderDiff =
+                new KernelCpuThreadReaderDiff(
+                        mKernelCpuThreadReader, MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT);
     }
 
     @Override
@@ -130,7 +133,7 @@
         mKernelCpuThreadReader.setNumBuckets(
                 parser.getInt(NUM_BUCKETS_SETTINGS_KEY, NUM_BUCKETS_DEFAULT));
         mKernelCpuThreadReader.setUidPredicate(uidPredicate);
-        mKernelCpuThreadReader.setMinimumTotalCpuUsageMillis(
+        mKernelCpuThreadReaderDiff.setMinimumTotalCpuUsageMillis(
                 parser.getInt(
                         MINIMUM_TOTAL_CPU_USAGE_MILLIS_SETTINGS_KEY,
                         MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT));
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java
new file mode 100644
index 0000000..460fe47
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mockitoSession;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import static java.util.stream.Collectors.toList;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KernelCpuThreadReaderDiffTest {
+
+    private MockitoSession mMockingSessions;
+    @Mock KernelCpuThreadReader mMockReader;
+
+    @Before
+    public void setUp() {
+        mMockingSessions = mockitoSession().initMocks(this).startMocking();
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockingSessions != null) {
+            mMockingSessions.finishMocking();
+        }
+    }
+
+    @Test
+    public void test_empty() {
+        KernelCpuThreadReaderDiff kernelCpuThreadReaderDiff =
+                new KernelCpuThreadReaderDiff(mMockReader, 0);
+        assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
+        assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isEmpty();
+    }
+
+    @Test
+    public void test_simple() {
+        when(mMockReader.getProcessCpuUsage())
+                .thenReturn(createProcess(new int[] {100, 100, 100}))
+                .thenReturn(createProcess(new int[] {150, 160, 170}));
+        KernelCpuThreadReaderDiff kernelCpuThreadReaderDiff =
+                new KernelCpuThreadReaderDiff(mMockReader, 0);
+        assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
+        assertThat(cpuUsages(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()))
+                .containsExactly(Arrays.asList(50, 60, 70));
+    }
+
+    @Test
+    public void test_failure() {
+        when(mMockReader.getProcessCpuUsage())
+                .thenReturn(createProcess(new int[] {1}))
+                .thenReturn(createProcess(new int[] {2}))
+                .thenThrow(new RuntimeException())
+                .thenReturn(createProcess(new int[] {4}))
+                .thenReturn(createProcess(new int[] {6}));
+        KernelCpuThreadReaderDiff kernelCpuThreadReaderDiff =
+                new KernelCpuThreadReaderDiff(mMockReader, 0);
+        assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
+        assertThat(cpuUsages(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()))
+                .containsExactly(Collections.singletonList(1));
+        assertThrows(
+                RuntimeException.class,
+                () -> cpuUsages(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()));
+        assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
+        assertThat(cpuUsages(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()))
+                .containsExactly(Collections.singletonList(2));
+    }
+
+    @Test
+    public void test_twoFailures() {
+        when(mMockReader.getProcessCpuUsage())
+                .thenReturn(createProcess(new int[] {1}))
+                .thenReturn(createProcess(new int[] {2}))
+                .thenThrow(new RuntimeException())
+                .thenThrow(new RuntimeException())
+                .thenReturn(createProcess(new int[] {4}))
+                .thenReturn(createProcess(new int[] {6}));
+        KernelCpuThreadReaderDiff kernelCpuThreadReaderDiff =
+                new KernelCpuThreadReaderDiff(mMockReader, 0);
+        assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
+        assertThat(cpuUsages(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()))
+                .containsExactly(Collections.singletonList(1));
+        assertThrows(
+                RuntimeException.class,
+                () -> cpuUsages(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()));
+        assertThrows(
+                RuntimeException.class,
+                () -> cpuUsages(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()));
+        assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
+        assertThat(cpuUsages(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()))
+                .containsExactly(Collections.singletonList(2));
+    }
+
+    @Test
+    public void test_negativeDiff() {
+        when(mMockReader.getProcessCpuUsage())
+                .thenReturn(createProcess(new int[] {2}))
+                .thenReturn(createProcess(new int[] {1}));
+        KernelCpuThreadReaderDiff kernelCpuThreadReaderDiff =
+                new KernelCpuThreadReaderDiff(mMockReader, 0);
+        assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
+        assertThat(cpuUsages(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()))
+                .containsExactly(Collections.singletonList(-1));
+    }
+
+    @Test
+    public void test_threshold() {
+        when(mMockReader.getProcessCpuUsage())
+                .thenReturn(createProcess(new int[] {1}))
+                .thenReturn(createProcess(new int[] {10}))
+                .thenReturn(createProcess(new int[] {12}))
+                .thenReturn(createProcess(new int[] {20}));
+        KernelCpuThreadReaderDiff kernelCpuThreadReaderDiff =
+                new KernelCpuThreadReaderDiff(mMockReader, 5);
+        assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
+
+        ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes1 =
+                kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed();
+        assertThat(cpuUsages(processes1)).containsExactly(Collections.singletonList(9));
+        assertThat(threadNames(processes1)).containsExactly("thread0");
+
+        ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes2 =
+                kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed();
+        assertThat(cpuUsages(processes2)).containsExactly(Collections.singletonList(2));
+        assertThat(threadNames(processes2)).containsExactly("__OTHER_THREADS");
+
+        ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes3 =
+                kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed();
+        assertThat(cpuUsages(processes3)).containsExactly(Collections.singletonList(8));
+        assertThat(threadNames(processes3)).containsExactly("thread0");
+    }
+
+    @Test
+    public void test_newThread() {
+        when(mMockReader.getProcessCpuUsage())
+                .thenReturn(createProcess(new int[] {1}))
+                .thenReturn(createProcess(new int[] {2}))
+                .thenReturn(createProcess(new int[] {4}, new int[] {5}));
+        KernelCpuThreadReaderDiff kernelCpuThreadReaderDiff =
+                new KernelCpuThreadReaderDiff(mMockReader, 0);
+        assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
+
+        ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes1 =
+                kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed();
+        assertThat(cpuUsages(processes1)).containsExactly(Collections.singletonList(1));
+        assertThat(threadNames(processes1)).containsExactly("thread0");
+
+        ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes2 =
+                kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed();
+        assertThat(cpuUsages(processes2))
+                .containsExactly(Collections.singletonList(2), Collections.singletonList(5));
+        assertThat(threadNames(processes2)).containsExactly("thread0", "thread1");
+    }
+
+    @Test
+    public void test_stoppedThread() {
+        when(mMockReader.getProcessCpuUsage())
+                .thenReturn(createProcess(new int[] {1}, new int[] {1}))
+                .thenReturn(createProcess(new int[] {2}, new int[] {3}))
+                .thenReturn(createProcess(new int[] {4}));
+        KernelCpuThreadReaderDiff kernelCpuThreadReaderDiff =
+                new KernelCpuThreadReaderDiff(mMockReader, 0);
+        assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
+
+        ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes1 =
+                kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed();
+        assertThat(cpuUsages(processes1))
+                .containsExactly(Collections.singletonList(1), Collections.singletonList(2));
+        assertThat(threadNames(processes1)).containsExactly("thread0", "thread1");
+
+        ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes2 =
+                kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed();
+        assertThat(cpuUsages(processes2)).containsExactly(Collections.singletonList(2));
+        assertThat(threadNames(processes2)).containsExactly("thread0");
+    }
+
+    private ArrayList<KernelCpuThreadReader.ProcessCpuUsage> createProcess(
+            int[]... cpuUsageMillis) {
+        ArrayList<KernelCpuThreadReader.ThreadCpuUsage> threadCpuUsages = new ArrayList<>();
+        for (int i = 0; i < cpuUsageMillis.length; i++) {
+            int[] cpuUsage = cpuUsageMillis[i];
+            threadCpuUsages.add(
+                    new KernelCpuThreadReader.ThreadCpuUsage(0, "thread" + i, cpuUsage));
+        }
+        return new ArrayList<>(
+                Collections.singletonList(
+                        new KernelCpuThreadReader.ProcessCpuUsage(
+                                0, "process", 0, threadCpuUsages)));
+    }
+
+    private Collection<Collection<Integer>> cpuUsages(
+            Collection<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages) {
+        return processCpuUsages.stream()
+                .flatMap(p -> p.threadCpuUsages.stream())
+                .map(t -> Arrays.stream(t.usageTimesMillis).boxed().collect(toList()))
+                .collect(toList());
+    }
+
+    private Collection<String> threadNames(
+            Collection<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages) {
+        return processCpuUsages.stream()
+                .flatMap(p -> p.threadCpuUsages.stream())
+                .map(t -> t.threadName)
+                .collect(toList());
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderEndToEndTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderEndToEndTest.java
index e9cad0a..d43989c 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderEndToEndTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderEndToEndTest.java
@@ -125,7 +125,7 @@
 
         // Get thread data from KernelCpuThreadReader
         final KernelCpuThreadReader kernelCpuThreadReader =
-                KernelCpuThreadReader.create(8, uid -> uid == Process.myUid(), 0);
+                KernelCpuThreadReader.create(8, uid -> uid == Process.myUid());
         assertNotNull(kernelCpuThreadReader);
         kernelCpuThreadReader.setUidPredicate(uid -> uid == Process.myUid());
         final Optional<ProcessCpuUsage> currentProcessCpuUsage =
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
index 61209e2..ae847c1 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
@@ -84,7 +84,6 @@
         final KernelCpuThreadReader kernelCpuThreadReader = new KernelCpuThreadReader(
                 8,
                 uidPredicate,
-                0,
                 mProcDirectory.toPath(),
                 mProcDirectory.toPath().resolve(uids[0] + "/task/" + uids[0] + "/time_in_state"),
                 processUtils);
@@ -103,90 +102,6 @@
         }
     }
 
-    @Test
-    public void testReader_filtersLowUsage() throws IOException {
-        int[] uids = new int[]{0, 1, 2, 3, 4};
-        int[] cpuUsage = new int[]{10, 0, 2, 100, 3};
-        int[] expectedUids = new int[]{0, 3, 4};
-        Predicate<Integer> uidPredicate = uid -> true;
-        KernelCpuThreadReader.Injector processUtils =
-                new KernelCpuThreadReader.Injector() {
-                    @Override
-                    public int getUidForPid(int pid) {
-                        return pid;
-                    }
-                };
-
-        for (int i = 0; i < uids.length; i++) {
-            int uid = uids[i];
-            setupDirectory(
-                    mProcDirectory.toPath().resolve(String.valueOf(uid)),
-                    new int[]{uid * 10},
-                    "process" + uid,
-                    new String[]{"thread" + uid},
-                    new int[]{1000},
-                    new int[][]{{cpuUsage[i]}});
-        }
-        final KernelCpuThreadReader kernelCpuThreadReader = new KernelCpuThreadReader(
-                8,
-                uidPredicate,
-                30,
-                mProcDirectory.toPath(),
-                mProcDirectory.toPath().resolve(uids[0] + "/task/" + uids[0] + "/time_in_state"),
-                processUtils);
-        ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsageByUids =
-                kernelCpuThreadReader.getProcessCpuUsage();
-        processCpuUsageByUids.sort(Comparator.comparing(usage -> usage.uid));
-
-        assertEquals(expectedUids.length, processCpuUsageByUids.size());
-        for (int i = 0; i < expectedUids.length; i++) {
-            KernelCpuThreadReader.ProcessCpuUsage processCpuUsage =
-                    processCpuUsageByUids.get(i);
-            assertEquals(expectedUids[i], processCpuUsage.uid);
-        }
-
-    }
-
-    @Test
-    public void testReader_otherThreads() throws IOException {
-        final Path processPath = mProcDirectory.toPath().resolve("1000");
-        setupDirectory(
-                processPath,
-                new int[]{1, 2, 3},
-                "process",
-                new String[]{"thread1", "thread2", "thread3"},
-                new int[]{1000, 2000},
-                new int[][]{{0, 100}, {10, 0}, {0, 300}});
-        KernelCpuThreadReader.Injector injector =
-                new KernelCpuThreadReader.Injector() {
-                    @Override
-                    public int getUidForPid(int pid) {
-                        return 0;
-                    }
-                };
-        final KernelCpuThreadReader kernelCpuThreadReader =
-                new KernelCpuThreadReader(
-                        8,
-                        uid -> true,
-                        2000,
-                        mProcDirectory.toPath(),
-                        processPath.resolve("task/1/time_in_state"),
-                        injector);
-        ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages =
-                kernelCpuThreadReader.getProcessCpuUsage();
-        assertEquals(1, processCpuUsages.size());
-        checkResults(
-                processCpuUsages.get(0),
-                kernelCpuThreadReader.getCpuFrequenciesKhz(),
-                0,
-                1000,
-                new int[] {-1, 3},
-                "process",
-                new String[] {"__OTHER_THREADS", "thread3"},
-                new int[] {1000, 2000},
-                new int[][] {{10, 100}, {0, 300}});
-    }
-
     private void setupDirectory(Path processPath, int[] threadIds, String processName,
             String[] threadNames, int[] cpuFrequencies, int[][] cpuTimes) throws IOException {
         // Make /proc/$PID
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 9f1eace..0e236ff 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -106,6 +106,7 @@
 import com.android.internal.os.BinderCallsStats.ExportedCallStat;
 import com.android.internal.os.KernelCpuSpeedReader;
 import com.android.internal.os.KernelCpuThreadReader;
+import com.android.internal.os.KernelCpuThreadReaderDiff;
 import com.android.internal.os.KernelCpuThreadReaderSettingsObserver;
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
@@ -257,7 +258,7 @@
     private StoragedUidIoStatsReader mStoragedUidIoStatsReader =
             new StoragedUidIoStatsReader();
     @Nullable
-    private final KernelCpuThreadReader mKernelCpuThreadReader;
+    private final KernelCpuThreadReaderDiff mKernelCpuThreadReader;
 
     private long mDebugElapsedClockPreviousValue = 0;
     private long mDebugElapsedClockPullCount = 0;
@@ -1721,7 +1722,7 @@
             throw new IllegalStateException("mKernelCpuThreadReader is null");
         }
         ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages =
-                this.mKernelCpuThreadReader.getProcessCpuUsage();
+                this.mKernelCpuThreadReader.getProcessCpuUsageDiffed();
         if (processCpuUsages == null) {
             throw new IllegalStateException("processCpuUsages is null");
         }