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