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