Merge "Add diffing to KernelCpuThreadReader"
This commit is contained in:
@@ -40,7 +40,7 @@ public class KernelCpuThreadReaderPerfTest {
|
||||
public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
|
||||
|
||||
private final KernelCpuThreadReader mKernelCpuThreadReader =
|
||||
KernelCpuThreadReader.create(8, uid -> 1000 <= uid && uid < 2000, 0);
|
||||
KernelCpuThreadReader.create(8, uid -> 1000 <= uid && uid < 2000);
|
||||
|
||||
@Test
|
||||
public void timeReadCurrentProcessCpuUsage() {
|
||||
|
||||
@@ -92,24 +92,12 @@ public class KernelCpuThreadReader {
|
||||
/** Value returned when there was an error getting an integer ID value (e.g. PID, UID) */
|
||||
private static final int ID_ERROR = -1;
|
||||
|
||||
/** Thread ID used when reporting CPU used by other threads */
|
||||
private static final int OTHER_THREADS_ID = -1;
|
||||
|
||||
/** Thread name used when reporting CPU used by other threads */
|
||||
private static final String OTHER_THREADS_NAME = "__OTHER_THREADS";
|
||||
|
||||
/**
|
||||
* When checking whether to report data for a thread, we check the UID of the thread's owner
|
||||
* against this predicate
|
||||
*/
|
||||
private Predicate<Integer> mUidPredicate;
|
||||
|
||||
/**
|
||||
* If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it
|
||||
* will not be reported
|
||||
*/
|
||||
private int mMinimumTotalCpuUsageMillis;
|
||||
|
||||
/** Where the proc filesystem is mounted */
|
||||
private final Path mProcPath;
|
||||
|
||||
@@ -138,13 +126,11 @@ public class KernelCpuThreadReader {
|
||||
public KernelCpuThreadReader(
|
||||
int numBuckets,
|
||||
Predicate<Integer> uidPredicate,
|
||||
int minimumTotalCpuUsageMillis,
|
||||
Path procPath,
|
||||
Path initialTimeInStatePath,
|
||||
Injector injector)
|
||||
throws IOException {
|
||||
mUidPredicate = uidPredicate;
|
||||
mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis;
|
||||
mProcPath = procPath;
|
||||
mProcTimeInStateReader = new ProcTimeInStateReader(initialTimeInStatePath);
|
||||
mInjector = injector;
|
||||
@@ -157,13 +143,11 @@ public class KernelCpuThreadReader {
|
||||
* @return the reader, null if an exception was thrown during creation
|
||||
*/
|
||||
@Nullable
|
||||
public static KernelCpuThreadReader create(
|
||||
int numBuckets, Predicate<Integer> uidPredicate, int minimumTotalCpuUsageMillis) {
|
||||
public static KernelCpuThreadReader create(int numBuckets, Predicate<Integer> uidPredicate) {
|
||||
try {
|
||||
return new KernelCpuThreadReader(
|
||||
numBuckets,
|
||||
uidPredicate,
|
||||
minimumTotalCpuUsageMillis,
|
||||
DEFAULT_PROC_PATH,
|
||||
DEFAULT_INITIAL_TIME_IN_STATE_PATH,
|
||||
new Injector());
|
||||
@@ -258,18 +242,6 @@ public class KernelCpuThreadReader {
|
||||
mUidPredicate = uidPredicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it
|
||||
* will not be reported
|
||||
*/
|
||||
void setMinimumTotalCpuUsageMillis(int minimumTotalCpuUsageMillis) {
|
||||
if (minimumTotalCpuUsageMillis < 0) {
|
||||
Slog.w(TAG, "Negative minimumTotalCpuUsageMillis: " + minimumTotalCpuUsageMillis);
|
||||
return;
|
||||
}
|
||||
mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all of the CPU usage statistics for each child thread of a process
|
||||
*
|
||||
@@ -292,7 +264,6 @@ public class KernelCpuThreadReader {
|
||||
+ uid);
|
||||
}
|
||||
|
||||
int[] filteredThreadsCpuUsage = null;
|
||||
final Path allThreadsPath = processPath.resolve("task");
|
||||
final ArrayList<ThreadCpuUsage> threadCpuUsages = new ArrayList<>();
|
||||
try (DirectoryStream<Path> threadPaths = Files.newDirectoryStream(allThreadsPath)) {
|
||||
@@ -301,14 +272,6 @@ public class KernelCpuThreadReader {
|
||||
if (threadCpuUsage == null) {
|
||||
continue;
|
||||
}
|
||||
if (mMinimumTotalCpuUsageMillis > totalCpuUsage(threadCpuUsage.usageTimesMillis)) {
|
||||
if (filteredThreadsCpuUsage == null) {
|
||||
filteredThreadsCpuUsage = new int[mFrequenciesKhz.length];
|
||||
}
|
||||
filteredThreadsCpuUsage =
|
||||
sumCpuUsage(filteredThreadsCpuUsage, threadCpuUsage.usageTimesMillis);
|
||||
continue;
|
||||
}
|
||||
threadCpuUsages.add(threadCpuUsage);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
@@ -320,14 +283,6 @@ public class KernelCpuThreadReader {
|
||||
if (threadCpuUsages.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add the filtered out thread CPU usage under an "other threads" ThreadCpuUsage
|
||||
if (filteredThreadsCpuUsage != null) {
|
||||
threadCpuUsages.add(
|
||||
new ThreadCpuUsage(
|
||||
OTHER_THREADS_ID, OTHER_THREADS_NAME, filteredThreadsCpuUsage));
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Read CPU usage of " + threadCpuUsages.size() + " threads");
|
||||
}
|
||||
@@ -404,25 +359,6 @@ public class KernelCpuThreadReader {
|
||||
}
|
||||
}
|
||||
|
||||
/** Get the sum of all CPU usage across all frequencies */
|
||||
@SuppressWarnings("ForLoopReplaceableByForEach")
|
||||
private static int totalCpuUsage(int[] cpuUsage) {
|
||||
int total = 0;
|
||||
for (int i = 0; i < cpuUsage.length; i++) {
|
||||
total += cpuUsage[i];
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/** Add two CPU frequency usages together */
|
||||
private static int[] sumCpuUsage(int[] a, int[] b) {
|
||||
int[] summed = new int[a.length];
|
||||
for (int i = 0; i < a.length; i++) {
|
||||
summed[i] = a[i] + b[i];
|
||||
}
|
||||
return summed;
|
||||
}
|
||||
|
||||
/** Puts frequencies and usage times into buckets */
|
||||
@VisibleForTesting
|
||||
public static class FrequencyBucketCreator {
|
||||
@@ -553,9 +489,10 @@ public class KernelCpuThreadReader {
|
||||
public final int processId;
|
||||
public final String processName;
|
||||
public final int uid;
|
||||
public final ArrayList<ThreadCpuUsage> threadCpuUsages;
|
||||
public ArrayList<ThreadCpuUsage> threadCpuUsages;
|
||||
|
||||
ProcessCpuUsage(
|
||||
@VisibleForTesting
|
||||
public ProcessCpuUsage(
|
||||
int processId,
|
||||
String processName,
|
||||
int uid,
|
||||
@@ -571,9 +508,10 @@ public class KernelCpuThreadReader {
|
||||
public static class ThreadCpuUsage {
|
||||
public final int threadId;
|
||||
public final String threadName;
|
||||
public final int[] usageTimesMillis;
|
||||
public int[] usageTimesMillis;
|
||||
|
||||
ThreadCpuUsage(int threadId, String threadName, int[] usageTimesMillis) {
|
||||
@VisibleForTesting
|
||||
public ThreadCpuUsage(int threadId, String threadName, int[] usageTimesMillis) {
|
||||
this.threadId = threadId;
|
||||
this.threadName = threadName;
|
||||
this.usageTimesMillis = usageTimesMillis;
|
||||
|
||||
305
core/java/com/android/internal/os/KernelCpuThreadReaderDiff.java
Normal file
305
core/java/com/android/internal/os/KernelCpuThreadReaderDiff.java
Normal file
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.internal.os;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Delegates per-thread CPU collection to {@link KernelCpuThreadReader}, and calculates the
|
||||
* difference between CPU usage at each call of {@link #getProcessCpuUsageDiffed()}.
|
||||
*
|
||||
* <p>Some notes on the diff calculation:
|
||||
*
|
||||
* <ul>
|
||||
* <li>The diffing is done between each call of {@link #getProcessCpuUsageDiffed()}, i.e. call N
|
||||
* of this method will return CPU used by threads between call N-1 and N.
|
||||
* <li>The first call of {@link #getProcessCpuUsageDiffed()} will return no processes ("first
|
||||
* call" is the first call in the lifetime of a {@link KernelCpuThreadReaderDiff} object).
|
||||
* <li>If a thread does not exist at call N, but does exist at call N+1, the diff will assume that
|
||||
* the CPU usage at call N was zero. Thus, the diff reported will be equivalent to the value
|
||||
* returned by {@link KernelCpuThreadReader#getProcessCpuUsage()} at call N+1.
|
||||
* <li>If an error occurs in {@link KernelCpuThreadReader} at call N, we will return no
|
||||
* information for CPU usage between call N-1 and N (as we don't know the start value) and
|
||||
* between N and N+1 (as we don't know the end value). Assuming all other calls are
|
||||
* successful, the next call to return data will be N+2, for the period between N+1 and N+2.
|
||||
* <li>If an error occurs in this class (but not in {@link KernelCpuThreadReader}) at call N, the
|
||||
* data will only be dropped for call N, as we can still use the CPU data for the surrounding
|
||||
* calls.
|
||||
* </ul>
|
||||
*
|
||||
* <p>Additionally to diffing, this class also contains logic for thresholding reported threads. A
|
||||
* thread will not be reported unless its total CPU usage is at least equal to the value set in
|
||||
* {@link #setMinimumTotalCpuUsageMillis}. Filtered thread CPU usage is summed and reported under
|
||||
* one "other threads" thread. This reduces the cardinality of the {@link
|
||||
* #getProcessCpuUsageDiffed()} result.
|
||||
*
|
||||
* <p>Thresholding is done in this class, instead of {@link KernelCpuThreadReader}, and instead of
|
||||
* WestWorld, because the thresholding should be done after diffing, not before. This is because of
|
||||
* two issues with thresholding before diffing:
|
||||
*
|
||||
* <ul>
|
||||
* <li>We would threshold less and less threads as thread uptime increases.
|
||||
* <li>We would encounter errors as the filtered threads become unfiltered, as the "other threads"
|
||||
* result could have negative diffs, and the newly unfiltered threads would have incorrect
|
||||
* diffs that include CPU usage from when they were filtered.
|
||||
* </ul>
|
||||
*
|
||||
* @hide Only for use within the system server
|
||||
*/
|
||||
@SuppressWarnings("ForLoopReplaceableByForEach")
|
||||
public class KernelCpuThreadReaderDiff {
|
||||
private static final String TAG = "KernelCpuThreadReaderDiff";
|
||||
|
||||
/** Thread ID used when reporting CPU used by other threads */
|
||||
private static final int OTHER_THREADS_ID = -1;
|
||||
|
||||
/** Thread name used when reporting CPU used by other threads */
|
||||
private static final String OTHER_THREADS_NAME = "__OTHER_THREADS";
|
||||
|
||||
private final KernelCpuThreadReader mReader;
|
||||
|
||||
/**
|
||||
* CPU usage from the previous call of {@link #getProcessCpuUsageDiffed()}. Null if there was no
|
||||
* previous call, or if the previous call failed
|
||||
*
|
||||
* <p>Maps the thread's identifier to the per-frequency CPU usage for that thread. The
|
||||
* identifier contains the minimal amount of information to identify a thread (see {@link
|
||||
* ThreadKey} for more information), thus reducing memory consumption.
|
||||
*/
|
||||
@Nullable private Map<ThreadKey, int[]> mPreviousCpuUsage;
|
||||
|
||||
/**
|
||||
* If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it
|
||||
* will not be reported
|
||||
*/
|
||||
private int mMinimumTotalCpuUsageMillis;
|
||||
|
||||
@VisibleForTesting
|
||||
public KernelCpuThreadReaderDiff(KernelCpuThreadReader reader, int minimumTotalCpuUsageMillis) {
|
||||
mReader = reader;
|
||||
mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis;
|
||||
mPreviousCpuUsage = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the difference in CPU usage since the last time this method was called.
|
||||
*
|
||||
* @see KernelCpuThreadReader#getProcessCpuUsage()
|
||||
*/
|
||||
@Nullable
|
||||
public ArrayList<KernelCpuThreadReader.ProcessCpuUsage> getProcessCpuUsageDiffed() {
|
||||
Map<ThreadKey, int[]> newCpuUsage = null;
|
||||
try {
|
||||
// Get the thread CPU usage and index them by ThreadKey
|
||||
final ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages =
|
||||
mReader.getProcessCpuUsage();
|
||||
newCpuUsage = createCpuUsageMap(processCpuUsages);
|
||||
// If there is no previous CPU usage, return nothing
|
||||
if (mPreviousCpuUsage == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Do diffing and thresholding for each process
|
||||
for (int i = 0; i < processCpuUsages.size(); i++) {
|
||||
KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = processCpuUsages.get(i);
|
||||
changeToDiffs(mPreviousCpuUsage, processCpuUsage);
|
||||
applyThresholding(processCpuUsage);
|
||||
}
|
||||
return processCpuUsages;
|
||||
} finally {
|
||||
// Always update the previous CPU usage. If we haven't got an update, it will be set to
|
||||
// null, so the next call knows there no previous values
|
||||
mPreviousCpuUsage = newCpuUsage;
|
||||
}
|
||||
}
|
||||
|
||||
/** @see KernelCpuThreadReader#getCpuFrequenciesKhz() */
|
||||
@Nullable
|
||||
public int[] getCpuFrequenciesKhz() {
|
||||
return mReader.getCpuFrequenciesKhz();
|
||||
}
|
||||
|
||||
/**
|
||||
* If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it
|
||||
* will not be reported
|
||||
*/
|
||||
void setMinimumTotalCpuUsageMillis(int minimumTotalCpuUsageMillis) {
|
||||
if (minimumTotalCpuUsageMillis < 0) {
|
||||
Slog.w(TAG, "Negative minimumTotalCpuUsageMillis: " + minimumTotalCpuUsageMillis);
|
||||
return;
|
||||
}
|
||||
mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a map of a thread's identifier to a thread's CPU usage. Used for fast indexing when
|
||||
* calculating diffs
|
||||
*/
|
||||
private static Map<ThreadKey, int[]> createCpuUsageMap(
|
||||
List<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages) {
|
||||
final Map<ThreadKey, int[]> cpuUsageMap = new ArrayMap<>();
|
||||
for (int i = 0; i < processCpuUsages.size(); i++) {
|
||||
KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = processCpuUsages.get(i);
|
||||
for (int j = 0; j < processCpuUsage.threadCpuUsages.size(); j++) {
|
||||
KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage =
|
||||
processCpuUsage.threadCpuUsages.get(j);
|
||||
cpuUsageMap.put(
|
||||
new ThreadKey(
|
||||
processCpuUsage.processId,
|
||||
threadCpuUsage.threadId,
|
||||
processCpuUsage.processName,
|
||||
threadCpuUsage.threadName),
|
||||
threadCpuUsage.usageTimesMillis);
|
||||
}
|
||||
}
|
||||
return cpuUsageMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the difference in per-frequency CPU usage for all threads in a process
|
||||
*
|
||||
* @param previousCpuUsage CPU usage from the last call, the base of the diff
|
||||
* @param processCpuUsage CPU usage from the current call, this value is modified to contain the
|
||||
* diffed values
|
||||
*/
|
||||
private static void changeToDiffs(
|
||||
Map<ThreadKey, int[]> previousCpuUsage,
|
||||
KernelCpuThreadReader.ProcessCpuUsage processCpuUsage) {
|
||||
for (int i = 0; i < processCpuUsage.threadCpuUsages.size(); i++) {
|
||||
KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage =
|
||||
processCpuUsage.threadCpuUsages.get(i);
|
||||
final ThreadKey key =
|
||||
new ThreadKey(
|
||||
processCpuUsage.processId,
|
||||
threadCpuUsage.threadId,
|
||||
processCpuUsage.processName,
|
||||
threadCpuUsage.threadName);
|
||||
int[] previous = previousCpuUsage.get(key);
|
||||
if (previous == null) {
|
||||
// If there's no previous CPU usage, assume that it's zero
|
||||
previous = new int[threadCpuUsage.usageTimesMillis.length];
|
||||
}
|
||||
threadCpuUsage.usageTimesMillis =
|
||||
cpuTimeDiff(threadCpuUsage.usageTimesMillis, previous);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out any threads with less than {@link #mMinimumTotalCpuUsageMillis} total CPU usage
|
||||
*
|
||||
* <p>The sum of the CPU usage of filtered threads is added under a single thread, labeled with
|
||||
* {@link #OTHER_THREADS_ID} and {@link #OTHER_THREADS_NAME}.
|
||||
*
|
||||
* @param processCpuUsage CPU usage to apply thresholding to, this value is modified to change
|
||||
* the threads it contains
|
||||
*/
|
||||
private void applyThresholding(KernelCpuThreadReader.ProcessCpuUsage processCpuUsage) {
|
||||
int[] filteredThreadsCpuUsage = null;
|
||||
final ArrayList<KernelCpuThreadReader.ThreadCpuUsage> thresholded = new ArrayList<>();
|
||||
for (int i = 0; i < processCpuUsage.threadCpuUsages.size(); i++) {
|
||||
KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage =
|
||||
processCpuUsage.threadCpuUsages.get(i);
|
||||
if (mMinimumTotalCpuUsageMillis > totalCpuUsage(threadCpuUsage.usageTimesMillis)) {
|
||||
if (filteredThreadsCpuUsage == null) {
|
||||
filteredThreadsCpuUsage = new int[threadCpuUsage.usageTimesMillis.length];
|
||||
}
|
||||
addToCpuUsage(filteredThreadsCpuUsage, threadCpuUsage.usageTimesMillis);
|
||||
continue;
|
||||
}
|
||||
thresholded.add(threadCpuUsage);
|
||||
}
|
||||
if (filteredThreadsCpuUsage != null) {
|
||||
thresholded.add(
|
||||
new KernelCpuThreadReader.ThreadCpuUsage(
|
||||
OTHER_THREADS_ID, OTHER_THREADS_NAME, filteredThreadsCpuUsage));
|
||||
}
|
||||
processCpuUsage.threadCpuUsages = thresholded;
|
||||
}
|
||||
|
||||
/** Get the sum of all CPU usage across all frequencies */
|
||||
private static int totalCpuUsage(int[] cpuUsage) {
|
||||
int total = 0;
|
||||
for (int i = 0; i < cpuUsage.length; i++) {
|
||||
total += cpuUsage[i];
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/** Add two CPU frequency usages together */
|
||||
private static void addToCpuUsage(int[] a, int[] b) {
|
||||
for (int i = 0; i < a.length; i++) {
|
||||
a[i] += b[i];
|
||||
}
|
||||
}
|
||||
|
||||
/** Subtract two CPU frequency usages from each other */
|
||||
private static int[] cpuTimeDiff(int[] a, int[] b) {
|
||||
int[] difference = new int[a.length];
|
||||
for (int i = 0; i < a.length; i++) {
|
||||
difference[i] = a[i] - b[i];
|
||||
}
|
||||
return difference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies a thread
|
||||
*
|
||||
* <p>Only stores the minimum amount of information to identify a thread. This includes the
|
||||
* PID/TID, but as both are recycled as processes/threads end and begin, we also store the hash
|
||||
* of the name of the process/thread.
|
||||
*/
|
||||
private static class ThreadKey {
|
||||
private final int mProcessId;
|
||||
private final int mThreadId;
|
||||
private final int mProcessNameHash;
|
||||
private final int mThreadNameHash;
|
||||
|
||||
ThreadKey(int processId, int threadId, String processName, String threadName) {
|
||||
this.mProcessId = processId;
|
||||
this.mThreadId = threadId;
|
||||
// Only store the hash to reduce memory consumption
|
||||
this.mProcessNameHash = Objects.hash(processName);
|
||||
this.mThreadNameHash = Objects.hash(threadName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mProcessId, mThreadId, mProcessNameHash, mThreadNameHash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof ThreadKey)) {
|
||||
return false;
|
||||
}
|
||||
ThreadKey other = (ThreadKey) obj;
|
||||
return mProcessId == other.mProcessId
|
||||
&& mThreadId == other.mThreadId
|
||||
&& mProcessNameHash == other.mProcessNameHash
|
||||
&& mThreadNameHash == other.mThreadNameHash;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,12 +67,14 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {
|
||||
|
||||
@Nullable private final KernelCpuThreadReader mKernelCpuThreadReader;
|
||||
|
||||
@Nullable private final KernelCpuThreadReaderDiff mKernelCpuThreadReaderDiff;
|
||||
|
||||
/**
|
||||
* @return returns a created {@link KernelCpuThreadReader} that will be modified by any change
|
||||
* in settings, returns null if creation failed
|
||||
*/
|
||||
@Nullable
|
||||
public static KernelCpuThreadReader getSettingsModifiedReader(Context context) {
|
||||
public static KernelCpuThreadReaderDiff getSettingsModifiedReader(Context context) {
|
||||
// Create the observer
|
||||
KernelCpuThreadReaderSettingsObserver settingsObserver =
|
||||
new KernelCpuThreadReaderSettingsObserver(context);
|
||||
@@ -82,7 +84,7 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {
|
||||
.registerContentObserver(
|
||||
settingsUri, false, settingsObserver, UserHandle.USER_SYSTEM);
|
||||
// Return the observer's reader
|
||||
return settingsObserver.mKernelCpuThreadReader;
|
||||
return settingsObserver.mKernelCpuThreadReaderDiff;
|
||||
}
|
||||
|
||||
private KernelCpuThreadReaderSettingsObserver(Context context) {
|
||||
@@ -90,9 +92,10 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {
|
||||
mContext = context;
|
||||
mKernelCpuThreadReader =
|
||||
KernelCpuThreadReader.create(
|
||||
NUM_BUCKETS_DEFAULT,
|
||||
UidPredicate.fromString(COLLECTED_UIDS_DEFAULT),
|
||||
MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT);
|
||||
NUM_BUCKETS_DEFAULT, UidPredicate.fromString(COLLECTED_UIDS_DEFAULT));
|
||||
mKernelCpuThreadReaderDiff =
|
||||
new KernelCpuThreadReaderDiff(
|
||||
mKernelCpuThreadReader, MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -130,7 +133,7 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {
|
||||
mKernelCpuThreadReader.setNumBuckets(
|
||||
parser.getInt(NUM_BUCKETS_SETTINGS_KEY, NUM_BUCKETS_DEFAULT));
|
||||
mKernelCpuThreadReader.setUidPredicate(uidPredicate);
|
||||
mKernelCpuThreadReader.setMinimumTotalCpuUsageMillis(
|
||||
mKernelCpuThreadReaderDiff.setMinimumTotalCpuUsageMillis(
|
||||
parser.getInt(
|
||||
MINIMUM_TOTAL_CPU_USAGE_MILLIS_SETTINGS_KEY,
|
||||
MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT));
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.internal.os;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.mockitoSession;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.testng.Assert.assertThrows;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoSession;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
@Presubmit
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class KernelCpuThreadReaderDiffTest {
|
||||
|
||||
private MockitoSession mMockingSessions;
|
||||
@Mock KernelCpuThreadReader mMockReader;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mMockingSessions = mockitoSession().initMocks(this).startMocking();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
if (mMockingSessions != null) {
|
||||
mMockingSessions.finishMocking();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_empty() {
|
||||
KernelCpuThreadReaderDiff kernelCpuThreadReaderDiff =
|
||||
new KernelCpuThreadReaderDiff(mMockReader, 0);
|
||||
assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
|
||||
assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_simple() {
|
||||
when(mMockReader.getProcessCpuUsage())
|
||||
.thenReturn(createProcess(new int[] {100, 100, 100}))
|
||||
.thenReturn(createProcess(new int[] {150, 160, 170}));
|
||||
KernelCpuThreadReaderDiff kernelCpuThreadReaderDiff =
|
||||
new KernelCpuThreadReaderDiff(mMockReader, 0);
|
||||
assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
|
||||
assertThat(cpuUsages(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()))
|
||||
.containsExactly(Arrays.asList(50, 60, 70));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_failure() {
|
||||
when(mMockReader.getProcessCpuUsage())
|
||||
.thenReturn(createProcess(new int[] {1}))
|
||||
.thenReturn(createProcess(new int[] {2}))
|
||||
.thenThrow(new RuntimeException())
|
||||
.thenReturn(createProcess(new int[] {4}))
|
||||
.thenReturn(createProcess(new int[] {6}));
|
||||
KernelCpuThreadReaderDiff kernelCpuThreadReaderDiff =
|
||||
new KernelCpuThreadReaderDiff(mMockReader, 0);
|
||||
assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
|
||||
assertThat(cpuUsages(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()))
|
||||
.containsExactly(Collections.singletonList(1));
|
||||
assertThrows(
|
||||
RuntimeException.class,
|
||||
() -> cpuUsages(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()));
|
||||
assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
|
||||
assertThat(cpuUsages(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()))
|
||||
.containsExactly(Collections.singletonList(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_twoFailures() {
|
||||
when(mMockReader.getProcessCpuUsage())
|
||||
.thenReturn(createProcess(new int[] {1}))
|
||||
.thenReturn(createProcess(new int[] {2}))
|
||||
.thenThrow(new RuntimeException())
|
||||
.thenThrow(new RuntimeException())
|
||||
.thenReturn(createProcess(new int[] {4}))
|
||||
.thenReturn(createProcess(new int[] {6}));
|
||||
KernelCpuThreadReaderDiff kernelCpuThreadReaderDiff =
|
||||
new KernelCpuThreadReaderDiff(mMockReader, 0);
|
||||
assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
|
||||
assertThat(cpuUsages(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()))
|
||||
.containsExactly(Collections.singletonList(1));
|
||||
assertThrows(
|
||||
RuntimeException.class,
|
||||
() -> cpuUsages(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()));
|
||||
assertThrows(
|
||||
RuntimeException.class,
|
||||
() -> cpuUsages(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()));
|
||||
assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
|
||||
assertThat(cpuUsages(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()))
|
||||
.containsExactly(Collections.singletonList(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_negativeDiff() {
|
||||
when(mMockReader.getProcessCpuUsage())
|
||||
.thenReturn(createProcess(new int[] {2}))
|
||||
.thenReturn(createProcess(new int[] {1}));
|
||||
KernelCpuThreadReaderDiff kernelCpuThreadReaderDiff =
|
||||
new KernelCpuThreadReaderDiff(mMockReader, 0);
|
||||
assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
|
||||
assertThat(cpuUsages(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()))
|
||||
.containsExactly(Collections.singletonList(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_threshold() {
|
||||
when(mMockReader.getProcessCpuUsage())
|
||||
.thenReturn(createProcess(new int[] {1}))
|
||||
.thenReturn(createProcess(new int[] {10}))
|
||||
.thenReturn(createProcess(new int[] {12}))
|
||||
.thenReturn(createProcess(new int[] {20}));
|
||||
KernelCpuThreadReaderDiff kernelCpuThreadReaderDiff =
|
||||
new KernelCpuThreadReaderDiff(mMockReader, 5);
|
||||
assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
|
||||
|
||||
ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes1 =
|
||||
kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed();
|
||||
assertThat(cpuUsages(processes1)).containsExactly(Collections.singletonList(9));
|
||||
assertThat(threadNames(processes1)).containsExactly("thread0");
|
||||
|
||||
ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes2 =
|
||||
kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed();
|
||||
assertThat(cpuUsages(processes2)).containsExactly(Collections.singletonList(2));
|
||||
assertThat(threadNames(processes2)).containsExactly("__OTHER_THREADS");
|
||||
|
||||
ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes3 =
|
||||
kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed();
|
||||
assertThat(cpuUsages(processes3)).containsExactly(Collections.singletonList(8));
|
||||
assertThat(threadNames(processes3)).containsExactly("thread0");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_newThread() {
|
||||
when(mMockReader.getProcessCpuUsage())
|
||||
.thenReturn(createProcess(new int[] {1}))
|
||||
.thenReturn(createProcess(new int[] {2}))
|
||||
.thenReturn(createProcess(new int[] {4}, new int[] {5}));
|
||||
KernelCpuThreadReaderDiff kernelCpuThreadReaderDiff =
|
||||
new KernelCpuThreadReaderDiff(mMockReader, 0);
|
||||
assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
|
||||
|
||||
ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes1 =
|
||||
kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed();
|
||||
assertThat(cpuUsages(processes1)).containsExactly(Collections.singletonList(1));
|
||||
assertThat(threadNames(processes1)).containsExactly("thread0");
|
||||
|
||||
ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes2 =
|
||||
kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed();
|
||||
assertThat(cpuUsages(processes2))
|
||||
.containsExactly(Collections.singletonList(2), Collections.singletonList(5));
|
||||
assertThat(threadNames(processes2)).containsExactly("thread0", "thread1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_stoppedThread() {
|
||||
when(mMockReader.getProcessCpuUsage())
|
||||
.thenReturn(createProcess(new int[] {1}, new int[] {1}))
|
||||
.thenReturn(createProcess(new int[] {2}, new int[] {3}))
|
||||
.thenReturn(createProcess(new int[] {4}));
|
||||
KernelCpuThreadReaderDiff kernelCpuThreadReaderDiff =
|
||||
new KernelCpuThreadReaderDiff(mMockReader, 0);
|
||||
assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
|
||||
|
||||
ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes1 =
|
||||
kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed();
|
||||
assertThat(cpuUsages(processes1))
|
||||
.containsExactly(Collections.singletonList(1), Collections.singletonList(2));
|
||||
assertThat(threadNames(processes1)).containsExactly("thread0", "thread1");
|
||||
|
||||
ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes2 =
|
||||
kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed();
|
||||
assertThat(cpuUsages(processes2)).containsExactly(Collections.singletonList(2));
|
||||
assertThat(threadNames(processes2)).containsExactly("thread0");
|
||||
}
|
||||
|
||||
private ArrayList<KernelCpuThreadReader.ProcessCpuUsage> createProcess(
|
||||
int[]... cpuUsageMillis) {
|
||||
ArrayList<KernelCpuThreadReader.ThreadCpuUsage> threadCpuUsages = new ArrayList<>();
|
||||
for (int i = 0; i < cpuUsageMillis.length; i++) {
|
||||
int[] cpuUsage = cpuUsageMillis[i];
|
||||
threadCpuUsages.add(
|
||||
new KernelCpuThreadReader.ThreadCpuUsage(0, "thread" + i, cpuUsage));
|
||||
}
|
||||
return new ArrayList<>(
|
||||
Collections.singletonList(
|
||||
new KernelCpuThreadReader.ProcessCpuUsage(
|
||||
0, "process", 0, threadCpuUsages)));
|
||||
}
|
||||
|
||||
private Collection<Collection<Integer>> cpuUsages(
|
||||
Collection<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages) {
|
||||
return processCpuUsages.stream()
|
||||
.flatMap(p -> p.threadCpuUsages.stream())
|
||||
.map(t -> Arrays.stream(t.usageTimesMillis).boxed().collect(toList()))
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
private Collection<String> threadNames(
|
||||
Collection<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages) {
|
||||
return processCpuUsages.stream()
|
||||
.flatMap(p -> p.threadCpuUsages.stream())
|
||||
.map(t -> t.threadName)
|
||||
.collect(toList());
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ public class KernelCpuThreadReaderEndToEndTest {
|
||||
|
||||
// Get thread data from KernelCpuThreadReader
|
||||
final KernelCpuThreadReader kernelCpuThreadReader =
|
||||
KernelCpuThreadReader.create(8, uid -> uid == Process.myUid(), 0);
|
||||
KernelCpuThreadReader.create(8, uid -> uid == Process.myUid());
|
||||
assertNotNull(kernelCpuThreadReader);
|
||||
kernelCpuThreadReader.setUidPredicate(uid -> uid == Process.myUid());
|
||||
final Optional<ProcessCpuUsage> currentProcessCpuUsage =
|
||||
|
||||
@@ -84,7 +84,6 @@ public class KernelCpuThreadReaderTest {
|
||||
final KernelCpuThreadReader kernelCpuThreadReader = new KernelCpuThreadReader(
|
||||
8,
|
||||
uidPredicate,
|
||||
0,
|
||||
mProcDirectory.toPath(),
|
||||
mProcDirectory.toPath().resolve(uids[0] + "/task/" + uids[0] + "/time_in_state"),
|
||||
processUtils);
|
||||
@@ -103,90 +102,6 @@ public class KernelCpuThreadReaderTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReader_filtersLowUsage() throws IOException {
|
||||
int[] uids = new int[]{0, 1, 2, 3, 4};
|
||||
int[] cpuUsage = new int[]{10, 0, 2, 100, 3};
|
||||
int[] expectedUids = new int[]{0, 3, 4};
|
||||
Predicate<Integer> uidPredicate = uid -> true;
|
||||
KernelCpuThreadReader.Injector processUtils =
|
||||
new KernelCpuThreadReader.Injector() {
|
||||
@Override
|
||||
public int getUidForPid(int pid) {
|
||||
return pid;
|
||||
}
|
||||
};
|
||||
|
||||
for (int i = 0; i < uids.length; i++) {
|
||||
int uid = uids[i];
|
||||
setupDirectory(
|
||||
mProcDirectory.toPath().resolve(String.valueOf(uid)),
|
||||
new int[]{uid * 10},
|
||||
"process" + uid,
|
||||
new String[]{"thread" + uid},
|
||||
new int[]{1000},
|
||||
new int[][]{{cpuUsage[i]}});
|
||||
}
|
||||
final KernelCpuThreadReader kernelCpuThreadReader = new KernelCpuThreadReader(
|
||||
8,
|
||||
uidPredicate,
|
||||
30,
|
||||
mProcDirectory.toPath(),
|
||||
mProcDirectory.toPath().resolve(uids[0] + "/task/" + uids[0] + "/time_in_state"),
|
||||
processUtils);
|
||||
ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsageByUids =
|
||||
kernelCpuThreadReader.getProcessCpuUsage();
|
||||
processCpuUsageByUids.sort(Comparator.comparing(usage -> usage.uid));
|
||||
|
||||
assertEquals(expectedUids.length, processCpuUsageByUids.size());
|
||||
for (int i = 0; i < expectedUids.length; i++) {
|
||||
KernelCpuThreadReader.ProcessCpuUsage processCpuUsage =
|
||||
processCpuUsageByUids.get(i);
|
||||
assertEquals(expectedUids[i], processCpuUsage.uid);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReader_otherThreads() throws IOException {
|
||||
final Path processPath = mProcDirectory.toPath().resolve("1000");
|
||||
setupDirectory(
|
||||
processPath,
|
||||
new int[]{1, 2, 3},
|
||||
"process",
|
||||
new String[]{"thread1", "thread2", "thread3"},
|
||||
new int[]{1000, 2000},
|
||||
new int[][]{{0, 100}, {10, 0}, {0, 300}});
|
||||
KernelCpuThreadReader.Injector injector =
|
||||
new KernelCpuThreadReader.Injector() {
|
||||
@Override
|
||||
public int getUidForPid(int pid) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
final KernelCpuThreadReader kernelCpuThreadReader =
|
||||
new KernelCpuThreadReader(
|
||||
8,
|
||||
uid -> true,
|
||||
2000,
|
||||
mProcDirectory.toPath(),
|
||||
processPath.resolve("task/1/time_in_state"),
|
||||
injector);
|
||||
ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages =
|
||||
kernelCpuThreadReader.getProcessCpuUsage();
|
||||
assertEquals(1, processCpuUsages.size());
|
||||
checkResults(
|
||||
processCpuUsages.get(0),
|
||||
kernelCpuThreadReader.getCpuFrequenciesKhz(),
|
||||
0,
|
||||
1000,
|
||||
new int[] {-1, 3},
|
||||
"process",
|
||||
new String[] {"__OTHER_THREADS", "thread3"},
|
||||
new int[] {1000, 2000},
|
||||
new int[][] {{10, 100}, {0, 300}});
|
||||
}
|
||||
|
||||
private void setupDirectory(Path processPath, int[] threadIds, String processName,
|
||||
String[] threadNames, int[] cpuFrequencies, int[][] cpuTimes) throws IOException {
|
||||
// Make /proc/$PID
|
||||
|
||||
@@ -110,6 +110,7 @@ import com.android.internal.os.BatteryStatsHelper;
|
||||
import com.android.internal.os.BinderCallsStats.ExportedCallStat;
|
||||
import com.android.internal.os.KernelCpuSpeedReader;
|
||||
import com.android.internal.os.KernelCpuThreadReader;
|
||||
import com.android.internal.os.KernelCpuThreadReaderDiff;
|
||||
import com.android.internal.os.KernelCpuThreadReaderSettingsObserver;
|
||||
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
|
||||
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
|
||||
@@ -262,7 +263,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
|
||||
private StoragedUidIoStatsReader mStoragedUidIoStatsReader =
|
||||
new StoragedUidIoStatsReader();
|
||||
@Nullable
|
||||
private final KernelCpuThreadReader mKernelCpuThreadReader;
|
||||
private final KernelCpuThreadReaderDiff mKernelCpuThreadReader;
|
||||
|
||||
private long mDebugElapsedClockPreviousValue = 0;
|
||||
private long mDebugElapsedClockPullCount = 0;
|
||||
@@ -1726,7 +1727,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
|
||||
throw new IllegalStateException("mKernelCpuThreadReader is null");
|
||||
}
|
||||
ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages =
|
||||
this.mKernelCpuThreadReader.getProcessCpuUsage();
|
||||
this.mKernelCpuThreadReader.getProcessCpuUsageDiffed();
|
||||
if (processCpuUsages == null) {
|
||||
throw new IllegalStateException("processCpuUsages is null");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user