1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.os;
18 
19 import android.annotation.Nullable;
20 import android.util.ArrayMap;
21 import android.util.Slog;
22 
23 import com.android.internal.annotations.VisibleForTesting;
24 
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Objects;
29 
30 /**
31  * Delegates per-thread CPU collection to {@link KernelCpuThreadReader}, and calculates the
32  * difference between CPU usage at each call of {@link #getProcessCpuUsageDiffed()}.
33  *
34  * <p>Some notes on the diff calculation:
35  *
36  * <ul>
37  *   <li>The diffing is done between each call of {@link #getProcessCpuUsageDiffed()}, i.e. call N
38  *       of this method will return CPU used by threads between call N-1 and N.
39  *   <li>The first call of {@link #getProcessCpuUsageDiffed()} will return no processes ("first
40  *       call" is the first call in the lifetime of a {@link KernelCpuThreadReaderDiff} object).
41  *   <li>If a thread does not exist at call N, but does exist at call N+1, the diff will assume that
42  *       the CPU usage at call N was zero. Thus, the diff reported will be equivalent to the value
43  *       returned by {@link KernelCpuThreadReader#getProcessCpuUsage()} at call N+1.
44  *   <li>If an error occurs in {@link KernelCpuThreadReader} at call N, we will return no
45  *       information for CPU usage between call N-1 and N (as we don't know the start value) and
46  *       between N and N+1 (as we don't know the end value). Assuming all other calls are
47  *       successful, the next call to return data will be N+2, for the period between N+1 and N+2.
48  *   <li>If an error occurs in this class (but not in {@link KernelCpuThreadReader}) at call N, the
49  *       data will only be dropped for call N, as we can still use the CPU data for the surrounding
50  *       calls.
51  * </ul>
52  *
53  * <p>Additionally to diffing, this class also contains logic for thresholding reported threads. A
54  * thread will not be reported unless its total CPU usage is at least equal to the value set in
55  * {@link #setMinimumTotalCpuUsageMillis}. Filtered thread CPU usage is summed and reported under
56  * one "other threads" thread. This reduces the cardinality of the {@link
57  * #getProcessCpuUsageDiffed()} result.
58  *
59  * <p>Thresholding is done in this class, instead of {@link KernelCpuThreadReader}, and instead of
60  * WestWorld, because the thresholding should be done after diffing, not before. This is because of
61  * two issues with thresholding before diffing:
62  *
63  * <ul>
64  *   <li>We would threshold less and less threads as thread uptime increases.
65  *   <li>We would encounter errors as the filtered threads become unfiltered, as the "other threads"
66  *       result could have negative diffs, and the newly unfiltered threads would have incorrect
67  *       diffs that include CPU usage from when they were filtered.
68  * </ul>
69  *
70  * @hide Only for use within the system server
71  */
72 @SuppressWarnings("ForLoopReplaceableByForEach")
73 public class KernelCpuThreadReaderDiff {
74     private static final String TAG = "KernelCpuThreadReaderDiff";
75 
76     /** Thread ID used when reporting CPU used by other threads */
77     private static final int OTHER_THREADS_ID = -1;
78 
79     /** Thread name used when reporting CPU used by other threads */
80     private static final String OTHER_THREADS_NAME = "__OTHER_THREADS";
81 
82     private final KernelCpuThreadReader mReader;
83 
84     /**
85      * CPU usage from the previous call of {@link #getProcessCpuUsageDiffed()}. Null if there was no
86      * previous call, or if the previous call failed
87      *
88      * <p>Maps the thread's identifier to the per-frequency CPU usage for that thread. The
89      * identifier contains the minimal amount of information to identify a thread (see {@link
90      * ThreadKey} for more information), thus reducing memory consumption.
91      */
92     @Nullable private Map<ThreadKey, int[]> mPreviousCpuUsage;
93 
94     /**
95      * If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it
96      * will not be reported
97      */
98     private int mMinimumTotalCpuUsageMillis;
99 
100     @VisibleForTesting
KernelCpuThreadReaderDiff(KernelCpuThreadReader reader, int minimumTotalCpuUsageMillis)101     public KernelCpuThreadReaderDiff(KernelCpuThreadReader reader, int minimumTotalCpuUsageMillis) {
102         mReader = reader;
103         mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis;
104         mPreviousCpuUsage = null;
105     }
106 
107     /**
108      * Returns the difference in CPU usage since the last time this method was called.
109      *
110      * @see KernelCpuThreadReader#getProcessCpuUsage()
111      */
112     @Nullable
getProcessCpuUsageDiffed()113     public ArrayList<KernelCpuThreadReader.ProcessCpuUsage> getProcessCpuUsageDiffed() {
114         Map<ThreadKey, int[]> newCpuUsage = null;
115         try {
116             // Get the thread CPU usage and index them by ThreadKey
117             final ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages =
118                     mReader.getProcessCpuUsage();
119             newCpuUsage = createCpuUsageMap(processCpuUsages);
120             // If there is no previous CPU usage, return nothing
121             if (mPreviousCpuUsage == null) {
122                 return null;
123             }
124 
125             // Do diffing and thresholding for each process
126             for (int i = 0; i < processCpuUsages.size(); i++) {
127                 KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = processCpuUsages.get(i);
128                 changeToDiffs(mPreviousCpuUsage, processCpuUsage);
129                 applyThresholding(processCpuUsage);
130             }
131             return processCpuUsages;
132         } finally {
133             // Always update the previous CPU usage. If we haven't got an update, it will be set to
134             // null, so the next call knows there no previous values
135             mPreviousCpuUsage = newCpuUsage;
136         }
137     }
138 
139     /** @see KernelCpuThreadReader#getCpuFrequenciesKhz() */
140     @Nullable
getCpuFrequenciesKhz()141     public int[] getCpuFrequenciesKhz() {
142         return mReader.getCpuFrequenciesKhz();
143     }
144 
145     /**
146      * If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it
147      * will not be reported
148      */
setMinimumTotalCpuUsageMillis(int minimumTotalCpuUsageMillis)149     void setMinimumTotalCpuUsageMillis(int minimumTotalCpuUsageMillis) {
150         if (minimumTotalCpuUsageMillis < 0) {
151             Slog.w(TAG, "Negative minimumTotalCpuUsageMillis: " + minimumTotalCpuUsageMillis);
152             return;
153         }
154         mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis;
155     }
156 
157     /**
158      * Create a map of a thread's identifier to a thread's CPU usage. Used for fast indexing when
159      * calculating diffs
160      */
createCpuUsageMap( List<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages)161     private static Map<ThreadKey, int[]> createCpuUsageMap(
162             List<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages) {
163         final Map<ThreadKey, int[]> cpuUsageMap = new ArrayMap<>();
164         for (int i = 0; i < processCpuUsages.size(); i++) {
165             KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = processCpuUsages.get(i);
166             for (int j = 0; j < processCpuUsage.threadCpuUsages.size(); j++) {
167                 KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage =
168                         processCpuUsage.threadCpuUsages.get(j);
169                 cpuUsageMap.put(
170                         new ThreadKey(
171                                 processCpuUsage.processId,
172                                 threadCpuUsage.threadId,
173                                 processCpuUsage.processName,
174                                 threadCpuUsage.threadName),
175                         threadCpuUsage.usageTimesMillis);
176             }
177         }
178         return cpuUsageMap;
179     }
180 
181     /**
182      * Calculate the difference in per-frequency CPU usage for all threads in a process
183      *
184      * @param previousCpuUsage CPU usage from the last call, the base of the diff
185      * @param processCpuUsage CPU usage from the current call, this value is modified to contain the
186      *     diffed values
187      */
changeToDiffs( Map<ThreadKey, int[]> previousCpuUsage, KernelCpuThreadReader.ProcessCpuUsage processCpuUsage)188     private static void changeToDiffs(
189             Map<ThreadKey, int[]> previousCpuUsage,
190             KernelCpuThreadReader.ProcessCpuUsage processCpuUsage) {
191         for (int i = 0; i < processCpuUsage.threadCpuUsages.size(); i++) {
192             KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage =
193                     processCpuUsage.threadCpuUsages.get(i);
194             final ThreadKey key =
195                     new ThreadKey(
196                             processCpuUsage.processId,
197                             threadCpuUsage.threadId,
198                             processCpuUsage.processName,
199                             threadCpuUsage.threadName);
200             int[] previous = previousCpuUsage.get(key);
201             if (previous == null) {
202                 // If there's no previous CPU usage, assume that it's zero
203                 previous = new int[threadCpuUsage.usageTimesMillis.length];
204             }
205             threadCpuUsage.usageTimesMillis =
206                     cpuTimeDiff(threadCpuUsage.usageTimesMillis, previous);
207         }
208     }
209 
210     /**
211      * Filter out any threads with less than {@link #mMinimumTotalCpuUsageMillis} total CPU usage
212      *
213      * <p>The sum of the CPU usage of filtered threads is added under a single thread, labeled with
214      * {@link #OTHER_THREADS_ID} and {@link #OTHER_THREADS_NAME}.
215      *
216      * @param processCpuUsage CPU usage to apply thresholding to, this value is modified to change
217      *     the threads it contains
218      */
applyThresholding(KernelCpuThreadReader.ProcessCpuUsage processCpuUsage)219     private void applyThresholding(KernelCpuThreadReader.ProcessCpuUsage processCpuUsage) {
220         int[] filteredThreadsCpuUsage = null;
221         final ArrayList<KernelCpuThreadReader.ThreadCpuUsage> thresholded = new ArrayList<>();
222         for (int i = 0; i < processCpuUsage.threadCpuUsages.size(); i++) {
223             KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage =
224                     processCpuUsage.threadCpuUsages.get(i);
225             if (mMinimumTotalCpuUsageMillis > totalCpuUsage(threadCpuUsage.usageTimesMillis)) {
226                 if (filteredThreadsCpuUsage == null) {
227                     filteredThreadsCpuUsage = new int[threadCpuUsage.usageTimesMillis.length];
228                 }
229                 addToCpuUsage(filteredThreadsCpuUsage, threadCpuUsage.usageTimesMillis);
230                 continue;
231             }
232             thresholded.add(threadCpuUsage);
233         }
234         if (filteredThreadsCpuUsage != null) {
235             thresholded.add(
236                     new KernelCpuThreadReader.ThreadCpuUsage(
237                             OTHER_THREADS_ID, OTHER_THREADS_NAME, filteredThreadsCpuUsage));
238         }
239         processCpuUsage.threadCpuUsages = thresholded;
240     }
241 
242     /** Get the sum of all CPU usage across all frequencies */
totalCpuUsage(int[] cpuUsage)243     private static int totalCpuUsage(int[] cpuUsage) {
244         int total = 0;
245         for (int i = 0; i < cpuUsage.length; i++) {
246             total += cpuUsage[i];
247         }
248         return total;
249     }
250 
251     /** Add two CPU frequency usages together */
addToCpuUsage(int[] a, int[] b)252     private static void addToCpuUsage(int[] a, int[] b) {
253         for (int i = 0; i < a.length; i++) {
254             a[i] += b[i];
255         }
256     }
257 
258     /** Subtract two CPU frequency usages from each other */
cpuTimeDiff(int[] a, int[] b)259     private static int[] cpuTimeDiff(int[] a, int[] b) {
260         int[] difference = new int[a.length];
261         for (int i = 0; i < a.length; i++) {
262             difference[i] = a[i] - b[i];
263         }
264         return difference;
265     }
266 
267     /**
268      * Identifies a thread
269      *
270      * <p>Only stores the minimum amount of information to identify a thread. This includes the
271      * PID/TID, but as both are recycled as processes/threads end and begin, we also store the hash
272      * of the name of the process/thread.
273      */
274     private static class ThreadKey {
275         private final int mProcessId;
276         private final int mThreadId;
277         private final int mProcessNameHash;
278         private final int mThreadNameHash;
279 
ThreadKey(int processId, int threadId, String processName, String threadName)280         ThreadKey(int processId, int threadId, String processName, String threadName) {
281             this.mProcessId = processId;
282             this.mThreadId = threadId;
283             // Only store the hash to reduce memory consumption
284             this.mProcessNameHash = Objects.hash(processName);
285             this.mThreadNameHash = Objects.hash(threadName);
286         }
287 
288         @Override
hashCode()289         public int hashCode() {
290             return Objects.hash(mProcessId, mThreadId, mProcessNameHash, mThreadNameHash);
291         }
292 
293         @Override
equals(Object obj)294         public boolean equals(Object obj) {
295             if (!(obj instanceof ThreadKey)) {
296                 return false;
297             }
298             ThreadKey other = (ThreadKey) obj;
299             return mProcessId == other.mProcessId
300                     && mThreadId == other.mThreadId
301                     && mProcessNameHash == other.mProcessNameHash
302                     && mThreadNameHash == other.mThreadNameHash;
303         }
304     }
305 }
306