blob: 3686048cee1584353cab2c910d73abbcd1205aad [file] [log] [blame]
Misha Wagner566903a2018-10-02 10:50:12 +01001/*
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
17package com.android.internal.os;
18
19import android.annotation.Nullable;
20import android.os.Process;
21import android.util.Slog;
22
23import com.android.internal.annotations.VisibleForTesting;
Misha Wagner0c5edc362018-10-24 11:28:57 +010024import com.android.internal.util.Preconditions;
Misha Wagner566903a2018-10-02 10:50:12 +010025
26import java.io.IOException;
27import java.nio.file.DirectoryStream;
28import java.nio.file.Files;
29import java.nio.file.Path;
30import java.nio.file.Paths;
31import java.util.ArrayList;
Misha Wagnerdc797542018-10-29 13:21:34 +000032import java.util.function.Predicate;
Misha Wagner566903a2018-10-02 10:50:12 +010033
34/**
Misha Wagner3989eb02019-03-19 11:05:37 +000035 * Iterates over processes, and all threads owned by those processes, and return the CPU usage for
36 * each thread. The CPU usage statistics contain the amount of time spent in a frequency band. CPU
37 * usage is collected using {@link ProcTimeInStateReader}.
38 *
39 * <p>We only collect CPU data for processes and threads that are owned by certain UIDs. These UIDs
40 * are configured via {@link #setUidPredicate}.
Misha Wagner0c5edc362018-10-24 11:28:57 +010041 *
42 * <p>Frequencies are bucketed together to reduce the amount of data created. This means that we
Misha Wagner3989eb02019-03-19 11:05:37 +000043 * return less frequencies than provided by {@link ProcTimeInStateReader}. The number of frequencies
44 * is configurable by {@link #setNumBuckets}. Frequencies are reported as the lowest frequency in
45 * that range. Frequencies are spread as evenly as possible across the buckets. The buckets do not
46 * cross over the little/big frequencies reported.
Misha Wagner0c5edc362018-10-24 11:28:57 +010047 *
48 * <p>N.B.: In order to bucket across little/big frequencies correctly, we assume that the {@code
49 * time_in_state} file contains every little core frequency in ascending order, followed by every
50 * big core frequency in ascending order. This assumption might not hold for devices with different
51 * kernel implementations of the {@code time_in_state} file generation.
Misha Wagner566903a2018-10-02 10:50:12 +010052 */
53public class KernelCpuThreadReader {
54
55 private static final String TAG = "KernelCpuThreadReader";
56
57 private static final boolean DEBUG = false;
58
59 /**
60 * The name of the file to read CPU statistics from, must be found in {@code
61 * /proc/$PID/task/$TID}
62 */
63 private static final String CPU_STATISTICS_FILENAME = "time_in_state";
64
65 /**
Misha Wagner3989eb02019-03-19 11:05:37 +000066 * The name of the file to read process command line invocation from, must be found in {@code
67 * /proc/$PID/}
Misha Wagner566903a2018-10-02 10:50:12 +010068 */
69 private static final String PROCESS_NAME_FILENAME = "cmdline";
70
71 /**
Misha Wagner3989eb02019-03-19 11:05:37 +000072 * The name of the file to read thread name from, must be found in {@code /proc/$PID/task/$TID}
Misha Wagner566903a2018-10-02 10:50:12 +010073 */
74 private static final String THREAD_NAME_FILENAME = "comm";
75
Misha Wagner3989eb02019-03-19 11:05:37 +000076 /** Glob pattern for the process directory names under {@code proc} */
Misha Wagnerdc797542018-10-29 13:21:34 +000077 private static final String PROCESS_DIRECTORY_FILTER = "[0-9]*";
78
Misha Wagner3989eb02019-03-19 11:05:37 +000079 /** Default process name when the name can't be read */
Misha Wagner566903a2018-10-02 10:50:12 +010080 private static final String DEFAULT_PROCESS_NAME = "unknown_process";
81
Misha Wagner3989eb02019-03-19 11:05:37 +000082 /** Default thread name when the name can't be read */
Misha Wagner566903a2018-10-02 10:50:12 +010083 private static final String DEFAULT_THREAD_NAME = "unknown_thread";
84
Misha Wagner3989eb02019-03-19 11:05:37 +000085 /** Default mount location of the {@code proc} filesystem */
Misha Wagner566903a2018-10-02 10:50:12 +010086 private static final Path DEFAULT_PROC_PATH = Paths.get("/proc");
87
Misha Wagner3989eb02019-03-19 11:05:37 +000088 /** The initial {@code time_in_state} file for {@link ProcTimeInStateReader} */
Misha Wagner566903a2018-10-02 10:50:12 +010089 private static final Path DEFAULT_INITIAL_TIME_IN_STATE_PATH =
90 DEFAULT_PROC_PATH.resolve("self/time_in_state");
91
Misha Wagner3989eb02019-03-19 11:05:37 +000092 /** Value returned when there was an error getting an integer ID value (e.g. PID, UID) */
Misha Wagnerdc797542018-10-29 13:21:34 +000093 private static final int ID_ERROR = -1;
94
Misha Wagnere51e5202019-02-26 15:21:45 +000095 /**
Misha Wagner4b32c9f2019-01-25 15:30:14 +000096 * When checking whether to report data for a thread, we check the UID of the thread's owner
97 * against this predicate
98 */
99 private Predicate<Integer> mUidPredicate;
100
Misha Wagner3989eb02019-03-19 11:05:37 +0000101 /** Where the proc filesystem is mounted */
Misha Wagner566903a2018-10-02 10:50:12 +0100102 private final Path mProcPath;
103
104 /**
105 * Frequencies read from the {@code time_in_state} file. Read from {@link
106 * #mProcTimeInStateReader#getCpuFrequenciesKhz()} and cast to {@code int[]}
107 */
Misha Wagner4b32c9f2019-01-25 15:30:14 +0000108 private int[] mFrequenciesKhz;
Misha Wagner566903a2018-10-02 10:50:12 +0100109
Misha Wagner3989eb02019-03-19 11:05:37 +0000110 /** Used to read and parse {@code time_in_state} files */
Misha Wagner566903a2018-10-02 10:50:12 +0100111 private final ProcTimeInStateReader mProcTimeInStateReader;
112
Misha Wagner3989eb02019-03-19 11:05:37 +0000113 /** Used to sort frequencies and usage times into buckets */
Misha Wagner4b32c9f2019-01-25 15:30:14 +0000114 private FrequencyBucketCreator mFrequencyBucketCreator;
Misha Wagner0c5edc362018-10-24 11:28:57 +0100115
Misha Wagnerdc797542018-10-29 13:21:34 +0000116 private final Injector mInjector;
117
Misha Wagner566903a2018-10-02 10:50:12 +0100118 /**
119 * Create with a path where `proc` is mounted. Used primarily for testing
120 *
Misha Wagner3989eb02019-03-19 11:05:37 +0000121 * @param procPath where `proc` is mounted (to find, see {@code mount | grep ^proc})
Misha Wagner566903a2018-10-02 10:50:12 +0100122 * @param initialTimeInStatePath where the initial {@code time_in_state} file exists to define
Misha Wagner3989eb02019-03-19 11:05:37 +0000123 * format
Misha Wagner566903a2018-10-02 10:50:12 +0100124 */
125 @VisibleForTesting
Misha Wagnerdc797542018-10-29 13:21:34 +0000126 public KernelCpuThreadReader(
Misha Wagner4b32c9f2019-01-25 15:30:14 +0000127 int numBuckets,
128 Predicate<Integer> uidPredicate,
Misha Wagnerdc797542018-10-29 13:21:34 +0000129 Path procPath,
130 Path initialTimeInStatePath,
Misha Wagner3989eb02019-03-19 11:05:37 +0000131 Injector injector)
132 throws IOException {
Misha Wagner4b32c9f2019-01-25 15:30:14 +0000133 mUidPredicate = uidPredicate;
Misha Wagner566903a2018-10-02 10:50:12 +0100134 mProcPath = procPath;
135 mProcTimeInStateReader = new ProcTimeInStateReader(initialTimeInStatePath);
Misha Wagnerdc797542018-10-29 13:21:34 +0000136 mInjector = injector;
Misha Wagner4b32c9f2019-01-25 15:30:14 +0000137 setNumBuckets(numBuckets);
Misha Wagner566903a2018-10-02 10:50:12 +0100138 }
139
140 /**
141 * Create the reader and handle exceptions during creation
142 *
143 * @return the reader, null if an exception was thrown during creation
144 */
145 @Nullable
Misha Wagner2487d3572019-03-18 17:30:14 +0000146 public static KernelCpuThreadReader create(int numBuckets, Predicate<Integer> uidPredicate) {
Misha Wagner566903a2018-10-02 10:50:12 +0100147 try {
Misha Wagner4b32c9f2019-01-25 15:30:14 +0000148 return new KernelCpuThreadReader(
149 numBuckets,
150 uidPredicate,
151 DEFAULT_PROC_PATH,
152 DEFAULT_INITIAL_TIME_IN_STATE_PATH,
153 new Injector());
Misha Wagner566903a2018-10-02 10:50:12 +0100154 } catch (IOException e) {
155 Slog.e(TAG, "Failed to initialize KernelCpuThreadReader", e);
156 return null;
157 }
158 }
159
160 /**
Misha Wagnerdc797542018-10-29 13:21:34 +0000161 * Get the per-thread CPU usage of all processes belonging to a set of UIDs
162 *
163 * <p>This function will crawl through all process {@code proc} directories found by the pattern
164 * {@code /proc/[0-9]*}, and then check the UID using {@code /proc/$PID/status}. This takes
Misha Wagner2d938f32019-03-13 17:55:29 +0000165 * approximately 500ms on a 2017 device. Therefore, this method can be computationally
166 * expensive, and should not be called more than once an hour.
Misha Wagnerdc797542018-10-29 13:21:34 +0000167 *
Misha Wagner4b32c9f2019-01-25 15:30:14 +0000168 * <p>Data is only collected for UIDs passing the predicate supplied in {@link
169 * #setUidPredicate}.
Misha Wagnerdc797542018-10-29 13:21:34 +0000170 */
171 @Nullable
Misha Wagner3989eb02019-03-19 11:05:37 +0000172 public ArrayList<ProcessCpuUsage> getProcessCpuUsage() {
Misha Wagnerdc797542018-10-29 13:21:34 +0000173 if (DEBUG) {
174 Slog.d(TAG, "Reading CPU thread usages for processes owned by UIDs");
175 }
176
177 final ArrayList<ProcessCpuUsage> processCpuUsages = new ArrayList<>();
178
179 try (DirectoryStream<Path> processPaths =
Misha Wagner3989eb02019-03-19 11:05:37 +0000180 Files.newDirectoryStream(mProcPath, PROCESS_DIRECTORY_FILTER)) {
Misha Wagnerdc797542018-10-29 13:21:34 +0000181 for (Path processPath : processPaths) {
182 final int processId = getProcessId(processPath);
183 final int uid = mInjector.getUidForPid(processId);
184 if (uid == ID_ERROR || processId == ID_ERROR) {
185 continue;
186 }
Misha Wagner4b32c9f2019-01-25 15:30:14 +0000187 if (!mUidPredicate.test(uid)) {
Misha Wagnerdc797542018-10-29 13:21:34 +0000188 continue;
189 }
190
191 final ProcessCpuUsage processCpuUsage =
192 getProcessCpuUsage(processPath, processId, uid);
193 if (processCpuUsage != null) {
194 processCpuUsages.add(processCpuUsage);
195 }
196 }
197 } catch (IOException e) {
Misha Wagner3989eb02019-03-19 11:05:37 +0000198 Slog.w(TAG, "Failed to iterate over process paths", e);
Misha Wagnerdc797542018-10-29 13:21:34 +0000199 return null;
200 }
201
202 if (processCpuUsages.isEmpty()) {
203 Slog.w(TAG, "Didn't successfully get any process CPU information for UIDs specified");
204 return null;
205 }
206
207 if (DEBUG) {
208 Slog.d(TAG, "Read usage for " + processCpuUsages.size() + " processes");
209 }
210
211 return processCpuUsages;
212 }
213
214 /**
Misha Wagner3989eb02019-03-19 11:05:37 +0000215 * Get the CPU frequencies that correspond to the times reported in {@link
216 * ThreadCpuUsage#usageTimesMillis}
Misha Wagner566903a2018-10-02 10:50:12 +0100217 */
218 @Nullable
Misha Wagner3989eb02019-03-19 11:05:37 +0000219 public int[] getCpuFrequenciesKhz() {
220 return mFrequenciesKhz;
221 }
222
223 /** Set the number of frequency buckets to use */
224 void setNumBuckets(int numBuckets) {
225 if (numBuckets < 1) {
226 Slog.w(TAG, "Number of buckets must be at least 1, but was " + numBuckets);
227 return;
228 }
229 // If `numBuckets` hasn't changed since the last set, do nothing
230 if (mFrequenciesKhz != null && mFrequenciesKhz.length == numBuckets) {
231 return;
232 }
233 mFrequencyBucketCreator =
234 new FrequencyBucketCreator(mProcTimeInStateReader.getFrequenciesKhz(), numBuckets);
235 mFrequenciesKhz =
236 mFrequencyBucketCreator.getBucketMinFrequencies(
237 mProcTimeInStateReader.getFrequenciesKhz());
238 }
239
240 /** Set the UID predicate for {@link #getProcessCpuUsage} */
241 void setUidPredicate(Predicate<Integer> uidPredicate) {
242 mUidPredicate = uidPredicate;
243 }
244
245 /**
Misha Wagner566903a2018-10-02 10:50:12 +0100246 * Read all of the CPU usage statistics for each child thread of a process
247 *
248 * @param processPath the {@code /proc} path of the thread
Misha Wagner3989eb02019-03-19 11:05:37 +0000249 * @param processId the ID of the process
250 * @param uid the ID of the user who owns the process
Misha Wagnerdc797542018-10-29 13:21:34 +0000251 * @return process CPU usage containing usage of all child threads. Null if the process exited
Misha Wagner3989eb02019-03-19 11:05:37 +0000252 * and its {@code proc} directory was removed while collecting information
Misha Wagner566903a2018-10-02 10:50:12 +0100253 */
254 @Nullable
255 private ProcessCpuUsage getProcessCpuUsage(Path processPath, int processId, int uid) {
256 if (DEBUG) {
Misha Wagner3989eb02019-03-19 11:05:37 +0000257 Slog.d(
258 TAG,
259 "Reading CPU thread usages with directory "
260 + processPath
261 + " process ID "
262 + processId
263 + " and user ID "
264 + uid);
Misha Wagner566903a2018-10-02 10:50:12 +0100265 }
266
267 final Path allThreadsPath = processPath.resolve("task");
268 final ArrayList<ThreadCpuUsage> threadCpuUsages = new ArrayList<>();
269 try (DirectoryStream<Path> threadPaths = Files.newDirectoryStream(allThreadsPath)) {
270 for (Path threadDirectory : threadPaths) {
271 ThreadCpuUsage threadCpuUsage = getThreadCpuUsage(threadDirectory);
Misha Wagnere51e5202019-02-26 15:21:45 +0000272 if (threadCpuUsage == null) {
273 continue;
Misha Wagner566903a2018-10-02 10:50:12 +0100274 }
Misha Wagnere51e5202019-02-26 15:21:45 +0000275 threadCpuUsages.add(threadCpuUsage);
Misha Wagner566903a2018-10-02 10:50:12 +0100276 }
277 } catch (IOException e) {
Misha Wagnercf7a07d2018-11-07 12:38:07 +0000278 // Expected when a process finishes
Misha Wagner566903a2018-10-02 10:50:12 +0100279 return null;
280 }
281
282 // If we found no threads, then the process has exited while we were reading from it
283 if (threadCpuUsages.isEmpty()) {
284 return null;
285 }
Misha Wagner566903a2018-10-02 10:50:12 +0100286 if (DEBUG) {
287 Slog.d(TAG, "Read CPU usage of " + threadCpuUsages.size() + " threads");
288 }
Misha Wagner3989eb02019-03-19 11:05:37 +0000289 return new ProcessCpuUsage(processId, getProcessName(processPath), uid, threadCpuUsages);
Misha Wagner566903a2018-10-02 10:50:12 +0100290 }
291
292 /**
293 * Get a thread's CPU usage
294 *
295 * @param threadDirectory the {@code /proc} directory of the thread
Misha Wagnerdc797542018-10-29 13:21:34 +0000296 * @return thread CPU usage. Null if the thread exited and its {@code proc} directory was
Misha Wagner3989eb02019-03-19 11:05:37 +0000297 * removed while collecting information
Misha Wagner566903a2018-10-02 10:50:12 +0100298 */
299 @Nullable
300 private ThreadCpuUsage getThreadCpuUsage(Path threadDirectory) {
301 // Get the thread ID from the directory name
302 final int threadId;
303 try {
304 final String directoryName = threadDirectory.getFileName().toString();
305 threadId = Integer.parseInt(directoryName);
306 } catch (NumberFormatException e) {
307 Slog.w(TAG, "Failed to parse thread ID when iterating over /proc/*/task", e);
308 return null;
309 }
310
311 // Get the thread name from the thread directory
312 final String threadName = getThreadName(threadDirectory);
313
314 // Get the CPU statistics from the directory
315 final Path threadCpuStatPath = threadDirectory.resolve(CPU_STATISTICS_FILENAME);
316 final long[] cpuUsagesLong = mProcTimeInStateReader.getUsageTimesMillis(threadCpuStatPath);
317 if (cpuUsagesLong == null) {
318 return null;
319 }
Misha Wagner0c5edc362018-10-24 11:28:57 +0100320 int[] cpuUsages = mFrequencyBucketCreator.getBucketedValues(cpuUsagesLong);
Misha Wagner566903a2018-10-02 10:50:12 +0100321
322 return new ThreadCpuUsage(threadId, threadName, cpuUsages);
323 }
324
Misha Wagner3989eb02019-03-19 11:05:37 +0000325 /** Get the command used to start a process */
Misha Wagner566903a2018-10-02 10:50:12 +0100326 private String getProcessName(Path processPath) {
327 final Path processNamePath = processPath.resolve(PROCESS_NAME_FILENAME);
328
Misha Wagner3989eb02019-03-19 11:05:37 +0000329 final String processName = ProcStatsUtil.readSingleLineProcFile(processNamePath.toString());
Misha Wagner566903a2018-10-02 10:50:12 +0100330 if (processName != null) {
331 return processName;
332 }
333 return DEFAULT_PROCESS_NAME;
334 }
335
Misha Wagner3989eb02019-03-19 11:05:37 +0000336 /** Get the name of a thread, given the {@code /proc} path of the thread */
Misha Wagner566903a2018-10-02 10:50:12 +0100337 private String getThreadName(Path threadPath) {
338 final Path threadNamePath = threadPath.resolve(THREAD_NAME_FILENAME);
Misha Wagner3989eb02019-03-19 11:05:37 +0000339 final String threadName = ProcStatsUtil.readNullSeparatedFile(threadNamePath.toString());
Misha Wagner566903a2018-10-02 10:50:12 +0100340 if (threadName == null) {
341 return DEFAULT_THREAD_NAME;
342 }
343 return threadName;
344 }
345
346 /**
Misha Wagnerdc797542018-10-29 13:21:34 +0000347 * Get the ID of a process from its path
348 *
349 * @param processPath {@code proc} path of the process
350 * @return the ID, {@link #ID_ERROR} if the path could not be parsed
351 */
352 private int getProcessId(Path processPath) {
353 String fileName = processPath.getFileName().toString();
354 try {
355 return Integer.parseInt(fileName);
356 } catch (NumberFormatException e) {
357 Slog.w(TAG, "Failed to parse " + fileName + " as process ID", e);
358 return ID_ERROR;
359 }
360 }
361
Misha Wagner3989eb02019-03-19 11:05:37 +0000362 /** Puts frequencies and usage times into buckets */
Misha Wagner0c5edc362018-10-24 11:28:57 +0100363 @VisibleForTesting
364 public static class FrequencyBucketCreator {
365 private final int mNumBuckets;
366 private final int mNumFrequencies;
367 private final int mBigFrequenciesStartIndex;
368 private final int mLittleNumBuckets;
369 private final int mBigNumBuckets;
370 private final int mLittleBucketSize;
371 private final int mBigBucketSize;
372
373 /**
374 * Buckets based of a list of frequencies
375 *
376 * @param frequencies the frequencies to base buckets off
Misha Wagner3989eb02019-03-19 11:05:37 +0000377 * @param numBuckets how many buckets to create
Misha Wagner0c5edc362018-10-24 11:28:57 +0100378 */
379 @VisibleForTesting
380 public FrequencyBucketCreator(long[] frequencies, int numBuckets) {
381 Preconditions.checkArgument(numBuckets > 0);
382
383 mNumFrequencies = frequencies.length;
384 mBigFrequenciesStartIndex = getBigFrequenciesStartIndex(frequencies);
385
386 final int littleNumBuckets;
387 final int bigNumBuckets;
388 if (mBigFrequenciesStartIndex < frequencies.length) {
389 littleNumBuckets = numBuckets / 2;
390 bigNumBuckets = numBuckets - littleNumBuckets;
391 } else {
392 // If we've got no big frequencies, set all buckets to little frequencies
393 littleNumBuckets = numBuckets;
394 bigNumBuckets = 0;
395 }
396
397 // Ensure that we don't have more buckets than frequencies
398 mLittleNumBuckets = Math.min(littleNumBuckets, mBigFrequenciesStartIndex);
Misha Wagner3989eb02019-03-19 11:05:37 +0000399 mBigNumBuckets =
400 Math.min(bigNumBuckets, frequencies.length - mBigFrequenciesStartIndex);
Misha Wagner0c5edc362018-10-24 11:28:57 +0100401 mNumBuckets = mLittleNumBuckets + mBigNumBuckets;
402
403 // Set the size of each little and big bucket. If they have no buckets, the size is zero
Misha Wagner3989eb02019-03-19 11:05:37 +0000404 mLittleBucketSize =
405 mLittleNumBuckets == 0 ? 0 : mBigFrequenciesStartIndex / mLittleNumBuckets;
406 mBigBucketSize =
407 mBigNumBuckets == 0
408 ? 0
409 : (frequencies.length - mBigFrequenciesStartIndex) / mBigNumBuckets;
Misha Wagner0c5edc362018-10-24 11:28:57 +0100410 }
411
Misha Wagner3989eb02019-03-19 11:05:37 +0000412 /** Find the index where frequencies change from little core to big core */
Misha Wagner0c5edc362018-10-24 11:28:57 +0100413 @VisibleForTesting
414 public static int getBigFrequenciesStartIndex(long[] frequenciesKhz) {
415 for (int i = 0; i < frequenciesKhz.length - 1; i++) {
416 if (frequenciesKhz[i] > frequenciesKhz[i + 1]) {
417 return i + 1;
418 }
419 }
420
421 return frequenciesKhz.length;
422 }
423
Misha Wagner3989eb02019-03-19 11:05:37 +0000424 /** Get the minimum frequency in each bucket */
Misha Wagner0c5edc362018-10-24 11:28:57 +0100425 @VisibleForTesting
426 public int[] getBucketMinFrequencies(long[] frequenciesKhz) {
427 Preconditions.checkArgument(frequenciesKhz.length == mNumFrequencies);
428 // If there's only one bucket, we bucket everything together so the first bucket is the
429 // min frequency
430 if (mNumBuckets == 1) {
Misha Wagner3989eb02019-03-19 11:05:37 +0000431 return new int[] {(int) frequenciesKhz[0]};
Misha Wagner0c5edc362018-10-24 11:28:57 +0100432 }
433
434 final int[] bucketMinFrequencies = new int[mNumBuckets];
435 // Initialize little buckets min frequencies
436 for (int i = 0; i < mLittleNumBuckets; i++) {
437 bucketMinFrequencies[i] = (int) frequenciesKhz[i * mLittleBucketSize];
438 }
439 // Initialize big buckets min frequencies
440 for (int i = 0; i < mBigNumBuckets; i++) {
441 final int frequencyIndex = mBigFrequenciesStartIndex + i * mBigBucketSize;
442 bucketMinFrequencies[mLittleNumBuckets + i] = (int) frequenciesKhz[frequencyIndex];
443 }
444 return bucketMinFrequencies;
445 }
446
447 /**
448 * Put an array of values into buckets. This takes a {@code long[]} and returns {@code
449 * int[]} as everywhere this method is used will have to do the conversion anyway, so we
450 * save time by doing it here instead
451 *
452 * @param values the values to bucket
453 * @return the bucketed usage times
454 */
455 @VisibleForTesting
Misha Wagner3989eb02019-03-19 11:05:37 +0000456 @SuppressWarnings("ForLoopReplaceableByForEach")
Misha Wagner0c5edc362018-10-24 11:28:57 +0100457 public int[] getBucketedValues(long[] values) {
458 Preconditions.checkArgument(values.length == mNumFrequencies);
459 final int[] bucketed = new int[mNumBuckets];
460
461 // If there's only one bucket, add all frequencies in
462 if (mNumBuckets == 1) {
463 for (int i = 0; i < values.length; i++) {
464 bucketed[0] += values[i];
465 }
466 return bucketed;
467 }
468
469 // Initialize the little buckets
470 for (int i = 0; i < mBigFrequenciesStartIndex; i++) {
471 final int bucketIndex = Math.min(i / mLittleBucketSize, mLittleNumBuckets - 1);
472 bucketed[bucketIndex] += values[i];
473 }
474 // Initialize the big buckets
475 for (int i = mBigFrequenciesStartIndex; i < values.length; i++) {
Misha Wagner3989eb02019-03-19 11:05:37 +0000476 final int bucketIndex =
477 Math.min(
478 mLittleNumBuckets
479 + (i - mBigFrequenciesStartIndex) / mBigBucketSize,
480 mNumBuckets - 1);
Misha Wagner0c5edc362018-10-24 11:28:57 +0100481 bucketed[bucketIndex] += values[i];
482 }
483 return bucketed;
484 }
485 }
486
Misha Wagner3989eb02019-03-19 11:05:37 +0000487 /** CPU usage of a process */
Misha Wagner566903a2018-10-02 10:50:12 +0100488 public static class ProcessCpuUsage {
489 public final int processId;
490 public final String processName;
491 public final int uid;
Misha Wagner2487d3572019-03-18 17:30:14 +0000492 public ArrayList<ThreadCpuUsage> threadCpuUsages;
Misha Wagner566903a2018-10-02 10:50:12 +0100493
Misha Wagner2487d3572019-03-18 17:30:14 +0000494 @VisibleForTesting
495 public ProcessCpuUsage(
Misha Wagner566903a2018-10-02 10:50:12 +0100496 int processId,
497 String processName,
498 int uid,
499 ArrayList<ThreadCpuUsage> threadCpuUsages) {
500 this.processId = processId;
501 this.processName = processName;
502 this.uid = uid;
503 this.threadCpuUsages = threadCpuUsages;
504 }
505 }
506
Misha Wagner3989eb02019-03-19 11:05:37 +0000507 /** CPU usage of a thread */
Misha Wagner566903a2018-10-02 10:50:12 +0100508 public static class ThreadCpuUsage {
509 public final int threadId;
510 public final String threadName;
Misha Wagner2487d3572019-03-18 17:30:14 +0000511 public int[] usageTimesMillis;
Misha Wagner566903a2018-10-02 10:50:12 +0100512
Misha Wagner2487d3572019-03-18 17:30:14 +0000513 @VisibleForTesting
514 public ThreadCpuUsage(int threadId, String threadName, int[] usageTimesMillis) {
Misha Wagner566903a2018-10-02 10:50:12 +0100515 this.threadId = threadId;
516 this.threadName = threadName;
517 this.usageTimesMillis = usageTimesMillis;
518 }
519 }
Misha Wagnerdc797542018-10-29 13:21:34 +0000520
Misha Wagner3989eb02019-03-19 11:05:37 +0000521 /** Used to inject static methods from {@link Process} */
Misha Wagnerdc797542018-10-29 13:21:34 +0000522 @VisibleForTesting
523 public static class Injector {
Misha Wagner3989eb02019-03-19 11:05:37 +0000524 /** Get the UID for the process with ID {@code pid} */
Misha Wagnerdc797542018-10-29 13:21:34 +0000525 public int getUidForPid(int pid) {
526 return Process.getUidForPid(pid);
527 }
528 }
Misha Wagner566903a2018-10-02 10:50:12 +0100529}