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