Clean up KernelCpuThreadReader
This includes (as well as nits by IntelliJ): - Removing getCurrentProcessCpuUsage, as it was only used in tests - Updating class documentation - Adding @Presubmit annotations to tests - Reformat using google-java-format Test: atest KernelCpuThreadReaderTest Change-Id: I710773043b9e9ef40545c20667fe2f79e9915e43
This commit is contained in:
@@ -47,7 +47,7 @@ public class KernelCpuThreadReaderPerfTest {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
assertNotNull(mKernelCpuThreadReader);
|
||||
while (state.keepRunning()) {
|
||||
this.mKernelCpuThreadReader.getCurrentProcessCpuUsage();
|
||||
this.mKernelCpuThreadReader.getProcessCpuUsage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,15 +32,18 @@ import java.util.ArrayList;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Given a process, will iterate over the child threads of the process, and return the CPU usage
|
||||
* statistics for each child thread. The CPU usage statistics contain the amount of time spent in a
|
||||
* frequency band.
|
||||
* Iterates over processes, and all threads owned by those processes, and return the CPU usage for
|
||||
* each thread. The CPU usage statistics contain the amount of time spent in a frequency band. CPU
|
||||
* usage is collected using {@link ProcTimeInStateReader}.
|
||||
*
|
||||
* <p>We only collect CPU data for processes and threads that are owned by certain UIDs. These UIDs
|
||||
* are configured via {@link #setUidPredicate}.
|
||||
*
|
||||
* <p>Frequencies are bucketed together to reduce the amount of data created. This means that we
|
||||
* return less frequencies than provided by {@link ProcTimeInStateReader}. The number of
|
||||
* frequencies is configurable by {@link #setNumBuckets}. Frequencies are reported as the lowest
|
||||
* frequency in that range. Frequencies are spread as evenly as possible across the buckets. The
|
||||
* buckets do not cross over the little/big frequencies reported.
|
||||
* return less frequencies than provided by {@link ProcTimeInStateReader}. The number of frequencies
|
||||
* is configurable by {@link #setNumBuckets}. Frequencies are reported as the lowest frequency in
|
||||
* that range. Frequencies are spread as evenly as possible across the buckets. The buckets do not
|
||||
* cross over the little/big frequencies reported.
|
||||
*
|
||||
* <p>N.B.: In order to bucket across little/big frequencies correctly, we assume that the {@code
|
||||
* time_in_state} file contains every little core frequency in ascending order, followed by every
|
||||
@@ -60,56 +63,39 @@ public class KernelCpuThreadReader {
|
||||
private static final String CPU_STATISTICS_FILENAME = "time_in_state";
|
||||
|
||||
/**
|
||||
* The name of the file to read process command line invocation from, must be found in
|
||||
* {@code /proc/$PID/}
|
||||
* The name of the file to read process command line invocation from, must be found in {@code
|
||||
* /proc/$PID/}
|
||||
*/
|
||||
private static final String PROCESS_NAME_FILENAME = "cmdline";
|
||||
|
||||
/**
|
||||
* The name of the file to read thread name from, must be found in
|
||||
* {@code /proc/$PID/task/$TID}
|
||||
* The name of the file to read thread name from, must be found in {@code /proc/$PID/task/$TID}
|
||||
*/
|
||||
private static final String THREAD_NAME_FILENAME = "comm";
|
||||
|
||||
/**
|
||||
* Glob pattern for the process directory names under {@code proc}
|
||||
*/
|
||||
/** Glob pattern for the process directory names under {@code proc} */
|
||||
private static final String PROCESS_DIRECTORY_FILTER = "[0-9]*";
|
||||
|
||||
/**
|
||||
* Default process name when the name can't be read
|
||||
*/
|
||||
/** Default process name when the name can't be read */
|
||||
private static final String DEFAULT_PROCESS_NAME = "unknown_process";
|
||||
|
||||
/**
|
||||
* Default thread name when the name can't be read
|
||||
*/
|
||||
/** Default thread name when the name can't be read */
|
||||
private static final String DEFAULT_THREAD_NAME = "unknown_thread";
|
||||
|
||||
/**
|
||||
* Default mount location of the {@code proc} filesystem
|
||||
*/
|
||||
/** Default mount location of the {@code proc} filesystem */
|
||||
private static final Path DEFAULT_PROC_PATH = Paths.get("/proc");
|
||||
|
||||
/**
|
||||
* The initial {@code time_in_state} file for {@link ProcTimeInStateReader}
|
||||
*/
|
||||
/** The initial {@code time_in_state} file for {@link ProcTimeInStateReader} */
|
||||
private static final Path DEFAULT_INITIAL_TIME_IN_STATE_PATH =
|
||||
DEFAULT_PROC_PATH.resolve("self/time_in_state");
|
||||
|
||||
/**
|
||||
* Value returned when there was an error getting an integer ID value (e.g. PID, UID)
|
||||
*/
|
||||
/** 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
|
||||
*/
|
||||
/** 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
|
||||
*/
|
||||
/** Thread name used when reporting CPU used by other threads */
|
||||
private static final String OTHER_THREADS_NAME = "__OTHER_THREADS";
|
||||
|
||||
/**
|
||||
@@ -124,9 +110,7 @@ public class KernelCpuThreadReader {
|
||||
*/
|
||||
private int mMinimumTotalCpuUsageMillis;
|
||||
|
||||
/**
|
||||
* Where the proc filesystem is mounted
|
||||
*/
|
||||
/** Where the proc filesystem is mounted */
|
||||
private final Path mProcPath;
|
||||
|
||||
/**
|
||||
@@ -135,14 +119,10 @@ public class KernelCpuThreadReader {
|
||||
*/
|
||||
private int[] mFrequenciesKhz;
|
||||
|
||||
/**
|
||||
* Used to read and parse {@code time_in_state} files
|
||||
*/
|
||||
/** Used to read and parse {@code time_in_state} files */
|
||||
private final ProcTimeInStateReader mProcTimeInStateReader;
|
||||
|
||||
/**
|
||||
* Used to sort frequencies and usage times into buckets
|
||||
*/
|
||||
/** Used to sort frequencies and usage times into buckets */
|
||||
private FrequencyBucketCreator mFrequencyBucketCreator;
|
||||
|
||||
private final Injector mInjector;
|
||||
@@ -150,10 +130,9 @@ public class KernelCpuThreadReader {
|
||||
/**
|
||||
* Create with a path where `proc` is mounted. Used primarily for testing
|
||||
*
|
||||
* @param procPath where `proc` is mounted (to find, see {@code mount | grep
|
||||
* ^proc})
|
||||
* @param procPath where `proc` is mounted (to find, see {@code mount | grep ^proc})
|
||||
* @param initialTimeInStatePath where the initial {@code time_in_state} file exists to define
|
||||
* format
|
||||
* format
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public KernelCpuThreadReader(
|
||||
@@ -162,7 +141,8 @@ public class KernelCpuThreadReader {
|
||||
int minimumTotalCpuUsageMillis,
|
||||
Path procPath,
|
||||
Path initialTimeInStatePath,
|
||||
Injector injector) throws IOException {
|
||||
Injector injector)
|
||||
throws IOException {
|
||||
mUidPredicate = uidPredicate;
|
||||
mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis;
|
||||
mProcPath = procPath;
|
||||
@@ -205,7 +185,7 @@ public class KernelCpuThreadReader {
|
||||
* #setUidPredicate}.
|
||||
*/
|
||||
@Nullable
|
||||
public ArrayList<ProcessCpuUsage> getProcessCpuUsageByUids() {
|
||||
public ArrayList<ProcessCpuUsage> getProcessCpuUsage() {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Reading CPU thread usages for processes owned by UIDs");
|
||||
}
|
||||
@@ -213,7 +193,7 @@ public class KernelCpuThreadReader {
|
||||
final ArrayList<ProcessCpuUsage> processCpuUsages = new ArrayList<>();
|
||||
|
||||
try (DirectoryStream<Path> processPaths =
|
||||
Files.newDirectoryStream(mProcPath, PROCESS_DIRECTORY_FILTER)) {
|
||||
Files.newDirectoryStream(mProcPath, PROCESS_DIRECTORY_FILTER)) {
|
||||
for (Path processPath : processPaths) {
|
||||
final int processId = getProcessId(processPath);
|
||||
final int uid = mInjector.getUidForPid(processId);
|
||||
@@ -231,7 +211,7 @@ public class KernelCpuThreadReader {
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Slog.w("Failed to iterate over process paths", e);
|
||||
Slog.w(TAG, "Failed to iterate over process paths", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -248,30 +228,68 @@ public class KernelCpuThreadReader {
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all of the CPU usage statistics for each child thread of the current process
|
||||
*
|
||||
* @return process CPU usage containing usage of all child threads
|
||||
* Get the CPU frequencies that correspond to the times reported in {@link
|
||||
* ThreadCpuUsage#usageTimesMillis}
|
||||
*/
|
||||
@Nullable
|
||||
public ProcessCpuUsage getCurrentProcessCpuUsage() {
|
||||
return getProcessCpuUsage(mProcPath.resolve("self"), mInjector.myPid(), mInjector.myUid());
|
||||
public int[] getCpuFrequenciesKhz() {
|
||||
return mFrequenciesKhz;
|
||||
}
|
||||
|
||||
/** Set the number of frequency buckets to use */
|
||||
void setNumBuckets(int numBuckets) {
|
||||
if (numBuckets < 1) {
|
||||
Slog.w(TAG, "Number of buckets must be at least 1, but was " + numBuckets);
|
||||
return;
|
||||
}
|
||||
// If `numBuckets` hasn't changed since the last set, do nothing
|
||||
if (mFrequenciesKhz != null && mFrequenciesKhz.length == numBuckets) {
|
||||
return;
|
||||
}
|
||||
mFrequencyBucketCreator =
|
||||
new FrequencyBucketCreator(mProcTimeInStateReader.getFrequenciesKhz(), numBuckets);
|
||||
mFrequenciesKhz =
|
||||
mFrequencyBucketCreator.getBucketMinFrequencies(
|
||||
mProcTimeInStateReader.getFrequenciesKhz());
|
||||
}
|
||||
|
||||
/** Set the UID predicate for {@link #getProcessCpuUsage} */
|
||||
void setUidPredicate(Predicate<Integer> uidPredicate) {
|
||||
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
|
||||
*
|
||||
* @param processPath the {@code /proc} path of the thread
|
||||
* @param processId the ID of the process
|
||||
* @param uid the ID of the user who owns the process
|
||||
* @param processId the ID of the process
|
||||
* @param uid the ID of the user who owns the process
|
||||
* @return process CPU usage containing usage of all child threads. Null if the process exited
|
||||
* and its {@code proc} directory was removed while collecting information
|
||||
* and its {@code proc} directory was removed while collecting information
|
||||
*/
|
||||
@Nullable
|
||||
private ProcessCpuUsage getProcessCpuUsage(Path processPath, int processId, int uid) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Reading CPU thread usages with directory " + processPath
|
||||
+ " process ID " + processId
|
||||
+ " and user ID " + uid);
|
||||
Slog.d(
|
||||
TAG,
|
||||
"Reading CPU thread usages with directory "
|
||||
+ processPath
|
||||
+ " process ID "
|
||||
+ processId
|
||||
+ " and user ID "
|
||||
+ uid);
|
||||
}
|
||||
|
||||
int[] filteredThreadsCpuUsage = null;
|
||||
@@ -305,64 +323,15 @@ public class KernelCpuThreadReader {
|
||||
|
||||
// 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));
|
||||
threadCpuUsages.add(
|
||||
new ThreadCpuUsage(
|
||||
OTHER_THREADS_ID, OTHER_THREADS_NAME, filteredThreadsCpuUsage));
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Read CPU usage of " + threadCpuUsages.size() + " threads");
|
||||
}
|
||||
return new ProcessCpuUsage(
|
||||
processId,
|
||||
getProcessName(processPath),
|
||||
uid,
|
||||
threadCpuUsages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of frequency buckets to use
|
||||
*/
|
||||
void setNumBuckets(int numBuckets) {
|
||||
if (numBuckets < 1) {
|
||||
Slog.w(TAG, "Number of buckets must be at least 1, but was " + numBuckets);
|
||||
return;
|
||||
}
|
||||
// If `numBuckets` hasn't changed since the last set, do nothing
|
||||
if (mFrequenciesKhz != null && mFrequenciesKhz.length == numBuckets) {
|
||||
return;
|
||||
}
|
||||
mFrequencyBucketCreator = new FrequencyBucketCreator(
|
||||
mProcTimeInStateReader.getFrequenciesKhz(), numBuckets);
|
||||
mFrequenciesKhz = mFrequencyBucketCreator.getBucketMinFrequencies(
|
||||
mProcTimeInStateReader.getFrequenciesKhz());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the UID predicate for {@link #getProcessCpuUsageByUids}
|
||||
*/
|
||||
void setUidPredicate(Predicate<Integer> uidPredicate) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CPU frequencies that correspond to the times reported in
|
||||
* {@link ThreadCpuUsage#usageTimesMillis}
|
||||
*/
|
||||
@Nullable
|
||||
public int[] getCpuFrequenciesKhz() {
|
||||
return mFrequenciesKhz;
|
||||
return new ProcessCpuUsage(processId, getProcessName(processPath), uid, threadCpuUsages);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -370,7 +339,7 @@ public class KernelCpuThreadReader {
|
||||
*
|
||||
* @param threadDirectory the {@code /proc} directory of the thread
|
||||
* @return thread CPU usage. Null if the thread exited and its {@code proc} directory was
|
||||
* removed while collecting information
|
||||
* removed while collecting information
|
||||
*/
|
||||
@Nullable
|
||||
private ThreadCpuUsage getThreadCpuUsage(Path threadDirectory) {
|
||||
@@ -398,27 +367,21 @@ public class KernelCpuThreadReader {
|
||||
return new ThreadCpuUsage(threadId, threadName, cpuUsages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the command used to start a process
|
||||
*/
|
||||
/** Get the command used to start a process */
|
||||
private String getProcessName(Path processPath) {
|
||||
final Path processNamePath = processPath.resolve(PROCESS_NAME_FILENAME);
|
||||
|
||||
final String processName =
|
||||
ProcStatsUtil.readSingleLineProcFile(processNamePath.toString());
|
||||
final String processName = ProcStatsUtil.readSingleLineProcFile(processNamePath.toString());
|
||||
if (processName != null) {
|
||||
return processName;
|
||||
}
|
||||
return DEFAULT_PROCESS_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of a thread, given the {@code /proc} path of the thread
|
||||
*/
|
||||
/** Get the name of a thread, given the {@code /proc} path of the thread */
|
||||
private String getThreadName(Path threadPath) {
|
||||
final Path threadNamePath = threadPath.resolve(THREAD_NAME_FILENAME);
|
||||
final String threadName =
|
||||
ProcStatsUtil.readNullSeparatedFile(threadNamePath.toString());
|
||||
final String threadName = ProcStatsUtil.readNullSeparatedFile(threadNamePath.toString());
|
||||
if (threadName == null) {
|
||||
return DEFAULT_THREAD_NAME;
|
||||
}
|
||||
@@ -441,9 +404,8 @@ public class KernelCpuThreadReader {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sum of all CPU usage across all frequencies
|
||||
*/
|
||||
/** 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++) {
|
||||
@@ -452,9 +414,7 @@ public class KernelCpuThreadReader {
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add two CPU frequency usages together
|
||||
*/
|
||||
/** 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++) {
|
||||
@@ -463,9 +423,7 @@ public class KernelCpuThreadReader {
|
||||
return summed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts frequencies and usage times into buckets
|
||||
*/
|
||||
/** Puts frequencies and usage times into buckets */
|
||||
@VisibleForTesting
|
||||
public static class FrequencyBucketCreator {
|
||||
private final int mNumBuckets;
|
||||
@@ -480,7 +438,7 @@ public class KernelCpuThreadReader {
|
||||
* Buckets based of a list of frequencies
|
||||
*
|
||||
* @param frequencies the frequencies to base buckets off
|
||||
* @param numBuckets how many buckets to create
|
||||
* @param numBuckets how many buckets to create
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public FrequencyBucketCreator(long[] frequencies, int numBuckets) {
|
||||
@@ -502,20 +460,20 @@ public class KernelCpuThreadReader {
|
||||
|
||||
// Ensure that we don't have more buckets than frequencies
|
||||
mLittleNumBuckets = Math.min(littleNumBuckets, mBigFrequenciesStartIndex);
|
||||
mBigNumBuckets = Math.min(
|
||||
bigNumBuckets, frequencies.length - mBigFrequenciesStartIndex);
|
||||
mBigNumBuckets =
|
||||
Math.min(bigNumBuckets, frequencies.length - mBigFrequenciesStartIndex);
|
||||
mNumBuckets = mLittleNumBuckets + mBigNumBuckets;
|
||||
|
||||
// Set the size of each little and big bucket. If they have no buckets, the size is zero
|
||||
mLittleBucketSize = mLittleNumBuckets == 0 ? 0 :
|
||||
mBigFrequenciesStartIndex / mLittleNumBuckets;
|
||||
mBigBucketSize = mBigNumBuckets == 0 ? 0 :
|
||||
(frequencies.length - mBigFrequenciesStartIndex) / mBigNumBuckets;
|
||||
mLittleBucketSize =
|
||||
mLittleNumBuckets == 0 ? 0 : mBigFrequenciesStartIndex / mLittleNumBuckets;
|
||||
mBigBucketSize =
|
||||
mBigNumBuckets == 0
|
||||
? 0
|
||||
: (frequencies.length - mBigFrequenciesStartIndex) / mBigNumBuckets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index where frequencies change from little core to big core
|
||||
*/
|
||||
/** Find the index where frequencies change from little core to big core */
|
||||
@VisibleForTesting
|
||||
public static int getBigFrequenciesStartIndex(long[] frequenciesKhz) {
|
||||
for (int i = 0; i < frequenciesKhz.length - 1; i++) {
|
||||
@@ -527,16 +485,14 @@ public class KernelCpuThreadReader {
|
||||
return frequenciesKhz.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minimum frequency in each bucket
|
||||
*/
|
||||
/** Get the minimum frequency in each bucket */
|
||||
@VisibleForTesting
|
||||
public int[] getBucketMinFrequencies(long[] frequenciesKhz) {
|
||||
Preconditions.checkArgument(frequenciesKhz.length == mNumFrequencies);
|
||||
// If there's only one bucket, we bucket everything together so the first bucket is the
|
||||
// min frequency
|
||||
if (mNumBuckets == 1) {
|
||||
return new int[]{(int) frequenciesKhz[0]};
|
||||
return new int[] {(int) frequenciesKhz[0]};
|
||||
}
|
||||
|
||||
final int[] bucketMinFrequencies = new int[mNumBuckets];
|
||||
@@ -561,6 +517,7 @@ public class KernelCpuThreadReader {
|
||||
* @return the bucketed usage times
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@SuppressWarnings("ForLoopReplaceableByForEach")
|
||||
public int[] getBucketedValues(long[] values) {
|
||||
Preconditions.checkArgument(values.length == mNumFrequencies);
|
||||
final int[] bucketed = new int[mNumBuckets];
|
||||
@@ -580,18 +537,18 @@ public class KernelCpuThreadReader {
|
||||
}
|
||||
// Initialize the big buckets
|
||||
for (int i = mBigFrequenciesStartIndex; i < values.length; i++) {
|
||||
final int bucketIndex = Math.min(
|
||||
mLittleNumBuckets + (i - mBigFrequenciesStartIndex) / mBigBucketSize,
|
||||
mNumBuckets - 1);
|
||||
final int bucketIndex =
|
||||
Math.min(
|
||||
mLittleNumBuckets
|
||||
+ (i - mBigFrequenciesStartIndex) / mBigBucketSize,
|
||||
mNumBuckets - 1);
|
||||
bucketed[bucketIndex] += values[i];
|
||||
}
|
||||
return bucketed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CPU usage of a process
|
||||
*/
|
||||
/** CPU usage of a process */
|
||||
public static class ProcessCpuUsage {
|
||||
public final int processId;
|
||||
public final String processName;
|
||||
@@ -610,46 +567,23 @@ public class KernelCpuThreadReader {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CPU usage of a thread
|
||||
*/
|
||||
/** CPU usage of a thread */
|
||||
public static class ThreadCpuUsage {
|
||||
public final int threadId;
|
||||
public final String threadName;
|
||||
public final int[] usageTimesMillis;
|
||||
|
||||
ThreadCpuUsage(
|
||||
int threadId,
|
||||
String threadName,
|
||||
int[] usageTimesMillis) {
|
||||
ThreadCpuUsage(int threadId, String threadName, int[] usageTimesMillis) {
|
||||
this.threadId = threadId;
|
||||
this.threadName = threadName;
|
||||
this.usageTimesMillis = usageTimesMillis;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to inject static methods from {@link Process}
|
||||
*/
|
||||
/** Used to inject static methods from {@link Process} */
|
||||
@VisibleForTesting
|
||||
public static class Injector {
|
||||
/**
|
||||
* Get the PID of the current process
|
||||
*/
|
||||
public int myPid() {
|
||||
return Process.myPid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the UID that owns the current process
|
||||
*/
|
||||
public int myUid() {
|
||||
return Process.myUid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the UID for the process with ID {@code pid}
|
||||
*/
|
||||
/** Get the UID for the process with ID {@code pid} */
|
||||
public int getUidForPid(int pid) {
|
||||
return Process.getUidForPid(pid);
|
||||
}
|
||||
|
||||
@@ -47,33 +47,29 @@ import java.util.regex.Pattern;
|
||||
public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {
|
||||
private static final String TAG = "KernelCpuThreadReaderSettingsObserver";
|
||||
|
||||
/**
|
||||
* The number of frequency buckets to report
|
||||
*/
|
||||
/** The number of frequency buckets to report */
|
||||
private static final String NUM_BUCKETS_SETTINGS_KEY = "num_buckets";
|
||||
|
||||
private static final int NUM_BUCKETS_DEFAULT = 8;
|
||||
|
||||
/**
|
||||
* List of UIDs to report data for
|
||||
*/
|
||||
/** List of UIDs to report data for */
|
||||
private static final String COLLECTED_UIDS_SETTINGS_KEY = "collected_uids";
|
||||
|
||||
private static final String COLLECTED_UIDS_DEFAULT = "0-0;1000-1000";
|
||||
|
||||
/**
|
||||
* Minimum total CPU usage to report
|
||||
*/
|
||||
/** Minimum total CPU usage to report */
|
||||
private static final String MINIMUM_TOTAL_CPU_USAGE_MILLIS_SETTINGS_KEY =
|
||||
"minimum_total_cpu_usage_millis";
|
||||
|
||||
private static final int MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT = 10000;
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
@Nullable
|
||||
private final KernelCpuThreadReader mKernelCpuThreadReader;
|
||||
@Nullable private final KernelCpuThreadReader mKernelCpuThreadReader;
|
||||
|
||||
/**
|
||||
* @return returns a created {@link KernelCpuThreadReader} that will be modified by any
|
||||
* change in settings, returns null if creation failed
|
||||
* @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) {
|
||||
@@ -81,10 +77,10 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {
|
||||
KernelCpuThreadReaderSettingsObserver settingsObserver =
|
||||
new KernelCpuThreadReaderSettingsObserver(context);
|
||||
// Register the observer to listen for setting changes
|
||||
Uri settingsUri =
|
||||
Settings.Global.getUriFor(Settings.Global.KERNEL_CPU_THREAD_READER);
|
||||
context.getContentResolver().registerContentObserver(
|
||||
settingsUri, false, settingsObserver, UserHandle.USER_SYSTEM);
|
||||
Uri settingsUri = Settings.Global.getUriFor(Settings.Global.KERNEL_CPU_THREAD_READER);
|
||||
context.getContentResolver()
|
||||
.registerContentObserver(
|
||||
settingsUri, false, settingsObserver, UserHandle.USER_SYSTEM);
|
||||
// Return the observer's reader
|
||||
return settingsObserver.mKernelCpuThreadReader;
|
||||
}
|
||||
@@ -92,10 +88,11 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {
|
||||
private KernelCpuThreadReaderSettingsObserver(Context context) {
|
||||
super(BackgroundThread.getHandler());
|
||||
mContext = context;
|
||||
mKernelCpuThreadReader = KernelCpuThreadReader.create(
|
||||
NUM_BUCKETS_DEFAULT,
|
||||
UidPredicate.fromString(COLLECTED_UIDS_DEFAULT),
|
||||
MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT);
|
||||
mKernelCpuThreadReader =
|
||||
KernelCpuThreadReader.create(
|
||||
NUM_BUCKETS_DEFAULT,
|
||||
UidPredicate.fromString(COLLECTED_UIDS_DEFAULT),
|
||||
MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -103,9 +100,7 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {
|
||||
updateReader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the reader with new settings
|
||||
*/
|
||||
/** Update the reader with new settings */
|
||||
private void updateReader() {
|
||||
if (mKernelCpuThreadReader == null) {
|
||||
return;
|
||||
@@ -113,8 +108,10 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {
|
||||
|
||||
final KeyValueListParser parser = new KeyValueListParser(',');
|
||||
try {
|
||||
parser.setString(Settings.Global.getString(
|
||||
mContext.getContentResolver(), Settings.Global.KERNEL_CPU_THREAD_READER));
|
||||
parser.setString(
|
||||
Settings.Global.getString(
|
||||
mContext.getContentResolver(),
|
||||
Settings.Global.KERNEL_CPU_THREAD_READER));
|
||||
} catch (IllegalArgumentException e) {
|
||||
Slog.e(TAG, "Bad settings", e);
|
||||
return;
|
||||
@@ -122,8 +119,9 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {
|
||||
|
||||
final UidPredicate uidPredicate;
|
||||
try {
|
||||
uidPredicate = UidPredicate.fromString(
|
||||
parser.getString(COLLECTED_UIDS_SETTINGS_KEY, COLLECTED_UIDS_DEFAULT));
|
||||
uidPredicate =
|
||||
UidPredicate.fromString(
|
||||
parser.getString(COLLECTED_UIDS_SETTINGS_KEY, COLLECTED_UIDS_DEFAULT));
|
||||
} catch (NumberFormatException e) {
|
||||
Slog.w(TAG, "Failed to get UID predicate", e);
|
||||
return;
|
||||
@@ -132,14 +130,13 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {
|
||||
mKernelCpuThreadReader.setNumBuckets(
|
||||
parser.getInt(NUM_BUCKETS_SETTINGS_KEY, NUM_BUCKETS_DEFAULT));
|
||||
mKernelCpuThreadReader.setUidPredicate(uidPredicate);
|
||||
mKernelCpuThreadReader.setMinimumTotalCpuUsageMillis(parser.getInt(
|
||||
MINIMUM_TOTAL_CPU_USAGE_MILLIS_SETTINGS_KEY,
|
||||
MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT));
|
||||
mKernelCpuThreadReader.setMinimumTotalCpuUsageMillis(
|
||||
parser.getInt(
|
||||
MINIMUM_TOTAL_CPU_USAGE_MILLIS_SETTINGS_KEY,
|
||||
MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a UID belongs to a set of UIDs
|
||||
*/
|
||||
/** Check whether a UID belongs to a set of UIDs */
|
||||
@VisibleForTesting
|
||||
public static class UidPredicate implements Predicate<Integer> {
|
||||
private static final Pattern UID_RANGE_PATTERN = Pattern.compile("([0-9]+)-([0-9]+)");
|
||||
@@ -150,14 +147,14 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {
|
||||
* Create a UID predicate from a string representing a list of UID ranges
|
||||
*
|
||||
* <p>UID ranges are a pair of integers separated by a '-'. If you want to specify a single
|
||||
* UID (e.g. UID 1000), you can use {@code 1000-1000}. Lists of ranges are separated by
|
||||
* a single ';'. For example, this would be a valid string representation: {@code
|
||||
* UID (e.g. UID 1000), you can use {@code 1000-1000}. Lists of ranges are separated by a
|
||||
* single ';'. For example, this would be a valid string representation: {@code
|
||||
* "1000-1999;2003-2003;2004-2004;2050-2060"}.
|
||||
*
|
||||
* <p>We do not use ',' to delimit as it is already used in separating different setting
|
||||
* arguments.
|
||||
*
|
||||
* @throws NumberFormatException if the input string is incorrectly formatted
|
||||
* @throws NumberFormatException if the input string is incorrectly formatted
|
||||
* @throws IllegalArgumentException if an UID range has a lower end than start
|
||||
*/
|
||||
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
|
||||
@@ -169,9 +166,10 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {
|
||||
throw new NumberFormatException(
|
||||
"Failed to recognize as number range: " + uidSpecifier);
|
||||
}
|
||||
acceptedUidRanges.add(Range.create(
|
||||
Integer.parseInt(uidRangeMatcher.group(1)),
|
||||
Integer.parseInt(uidRangeMatcher.group(2))));
|
||||
acceptedUidRanges.add(
|
||||
Range.create(
|
||||
Integer.parseInt(uidRangeMatcher.group(1)),
|
||||
Integer.parseInt(uidRangeMatcher.group(2))));
|
||||
}
|
||||
return new UidPredicate(acceptedUidRanges);
|
||||
}
|
||||
@@ -181,6 +179,7 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("ForLoopReplaceableByForEach")
|
||||
public boolean test(Integer uid) {
|
||||
for (int i = 0; i < mAcceptedUidRanges.size(); i++) {
|
||||
if (mAcceptedUidRanges.get(i).contains(uid)) {
|
||||
|
||||
@@ -35,6 +35,7 @@ import org.junit.runner.RunWith;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalDouble;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.function.Consumer;
|
||||
@@ -126,17 +127,21 @@ public class KernelCpuThreadReaderEndToEndTest {
|
||||
final KernelCpuThreadReader kernelCpuThreadReader =
|
||||
KernelCpuThreadReader.create(8, uid -> uid == Process.myUid(), 0);
|
||||
assertNotNull(kernelCpuThreadReader);
|
||||
final ProcessCpuUsage currentProcessCpuUsage =
|
||||
kernelCpuThreadReader.getCurrentProcessCpuUsage();
|
||||
kernelCpuThreadReader.setUidPredicate(uid -> uid == Process.myUid());
|
||||
final Optional<ProcessCpuUsage> currentProcessCpuUsage =
|
||||
kernelCpuThreadReader.getProcessCpuUsage().stream()
|
||||
.filter(p -> p.processId == Process.myPid())
|
||||
.findFirst();
|
||||
assertTrue(currentProcessCpuUsage.isPresent());
|
||||
|
||||
// Threads can terminate, as we've finished crawling them from /proc
|
||||
threadFinishedLatch.countDown();
|
||||
|
||||
// Check that we've got times for every thread we spawned
|
||||
final List<ThreadCpuUsage> threadCpuUsages = currentProcessCpuUsage.threadCpuUsages
|
||||
.stream()
|
||||
.filter((thread) -> thread.threadName.startsWith(tag))
|
||||
.collect(Collectors.toList());
|
||||
final List<ThreadCpuUsage> threadCpuUsages =
|
||||
currentProcessCpuUsage.get().threadCpuUsages.stream()
|
||||
.filter((thread) -> thread.threadName.startsWith(tag))
|
||||
.collect(Collectors.toList());
|
||||
assertEquals(
|
||||
"Incorrect number of threads returned by KernelCpuThreadReader",
|
||||
numSamples, threadCpuUsages.size());
|
||||
|
||||
@@ -25,6 +25,7 @@ import static org.testng.Assert.assertThrows;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.FileUtils;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.SmallTest;
|
||||
@@ -44,27 +45,10 @@ import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Presubmit
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class KernelCpuThreadReaderTest {
|
||||
|
||||
private static final int UID = 1000;
|
||||
private static final int PROCESS_ID = 1234;
|
||||
private static final int[] THREAD_IDS = {0, 1000, 1235, 4321};
|
||||
private static final String PROCESS_NAME = "test_process";
|
||||
private static final String[] THREAD_NAMES = {
|
||||
"test_thread_1", "test_thread_2", "test_thread_3", "test_thread_4"
|
||||
};
|
||||
private static final int[] THREAD_CPU_FREQUENCIES = {
|
||||
1000, 2000, 3000, 4000,
|
||||
};
|
||||
private static final int[][] THREAD_CPU_TIMES = {
|
||||
{1, 0, 0, 1},
|
||||
{0, 0, 0, 0},
|
||||
{1000, 1000, 1000, 1000},
|
||||
{0, 1, 2, 3},
|
||||
};
|
||||
|
||||
private File mProcDirectory;
|
||||
|
||||
@Before
|
||||
@@ -78,41 +62,6 @@ public class KernelCpuThreadReaderTest {
|
||||
FileUtils.deleteContents(mProcDirectory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReader_currentProcess() throws IOException {
|
||||
KernelCpuThreadReader.Injector processUtils =
|
||||
new KernelCpuThreadReader.Injector() {
|
||||
@Override
|
||||
public int myPid() {
|
||||
return PROCESS_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int myUid() {
|
||||
return UID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUidForPid(int pid) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
setupDirectory(mProcDirectory.toPath().resolve("self"), THREAD_IDS, PROCESS_NAME,
|
||||
THREAD_NAMES, THREAD_CPU_FREQUENCIES, THREAD_CPU_TIMES);
|
||||
|
||||
final KernelCpuThreadReader kernelCpuThreadReader = new KernelCpuThreadReader(
|
||||
8,
|
||||
uid -> 1000 <= uid && uid < 2000,
|
||||
0,
|
||||
mProcDirectory.toPath(),
|
||||
mProcDirectory.toPath().resolve("self/task/" + THREAD_IDS[0] + "/time_in_state"),
|
||||
processUtils);
|
||||
final KernelCpuThreadReader.ProcessCpuUsage processCpuUsage =
|
||||
kernelCpuThreadReader.getCurrentProcessCpuUsage();
|
||||
checkResults(processCpuUsage, kernelCpuThreadReader.getCpuFrequenciesKhz(), UID, PROCESS_ID,
|
||||
THREAD_IDS, PROCESS_NAME, THREAD_NAMES, THREAD_CPU_FREQUENCIES, THREAD_CPU_TIMES);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReader_byUids() throws IOException {
|
||||
int[] uids = new int[]{0, 2, 3, 4, 5, 6000};
|
||||
@@ -120,16 +69,6 @@ public class KernelCpuThreadReaderTest {
|
||||
int[] expectedUids = new int[]{0, 4, 5, 6000};
|
||||
KernelCpuThreadReader.Injector processUtils =
|
||||
new KernelCpuThreadReader.Injector() {
|
||||
@Override
|
||||
public int myPid() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int myUid() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUidForPid(int pid) {
|
||||
return pid;
|
||||
@@ -150,7 +89,7 @@ public class KernelCpuThreadReaderTest {
|
||||
mProcDirectory.toPath().resolve(uids[0] + "/task/" + uids[0] + "/time_in_state"),
|
||||
processUtils);
|
||||
ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsageByUids =
|
||||
kernelCpuThreadReader.getProcessCpuUsageByUids();
|
||||
kernelCpuThreadReader.getProcessCpuUsage();
|
||||
processCpuUsageByUids.sort(Comparator.comparing(usage -> usage.processId));
|
||||
|
||||
assertEquals(expectedUids.length, processCpuUsageByUids.size());
|
||||
@@ -172,16 +111,6 @@ public class KernelCpuThreadReaderTest {
|
||||
Predicate<Integer> uidPredicate = uid -> true;
|
||||
KernelCpuThreadReader.Injector processUtils =
|
||||
new KernelCpuThreadReader.Injector() {
|
||||
@Override
|
||||
public int myPid() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int myUid() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUidForPid(int pid) {
|
||||
return pid;
|
||||
@@ -206,7 +135,7 @@ public class KernelCpuThreadReaderTest {
|
||||
mProcDirectory.toPath().resolve(uids[0] + "/task/" + uids[0] + "/time_in_state"),
|
||||
processUtils);
|
||||
ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsageByUids =
|
||||
kernelCpuThreadReader.getProcessCpuUsageByUids();
|
||||
kernelCpuThreadReader.getProcessCpuUsage();
|
||||
processCpuUsageByUids.sort(Comparator.comparing(usage -> usage.uid));
|
||||
|
||||
assertEquals(expectedUids.length, processCpuUsageByUids.size());
|
||||
@@ -220,7 +149,7 @@ public class KernelCpuThreadReaderTest {
|
||||
|
||||
@Test
|
||||
public void testReader_otherThreads() throws IOException {
|
||||
final Path processPath = mProcDirectory.toPath().resolve("self");
|
||||
final Path processPath = mProcDirectory.toPath().resolve("1000");
|
||||
setupDirectory(
|
||||
processPath,
|
||||
new int[]{1, 2, 3},
|
||||
@@ -228,39 +157,34 @@ public class KernelCpuThreadReaderTest {
|
||||
new String[]{"thread1", "thread2", "thread3"},
|
||||
new int[]{1000, 2000},
|
||||
new int[][]{{0, 100}, {10, 0}, {0, 300}});
|
||||
final KernelCpuThreadReader kernelCpuThreadReader = new KernelCpuThreadReader(
|
||||
8,
|
||||
i -> true,
|
||||
2000,
|
||||
mProcDirectory.toPath(),
|
||||
processPath.resolve("task/1/time_in_state"),
|
||||
KernelCpuThreadReader.Injector injector =
|
||||
new KernelCpuThreadReader.Injector() {
|
||||
@Override
|
||||
public int myPid() {
|
||||
return 1000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int myUid() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@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(
|
||||
kernelCpuThreadReader.getCurrentProcessCpuUsage(),
|
||||
processCpuUsages.get(0),
|
||||
kernelCpuThreadReader.getCpuFrequenciesKhz(),
|
||||
0,
|
||||
1000,
|
||||
new int[]{-1, 3},
|
||||
new int[] {-1, 3},
|
||||
"process",
|
||||
new String[]{"__OTHER_THREADS", "thread3"},
|
||||
new int[]{1000, 2000},
|
||||
new int[][]{{100, 1000}, {0, 3000}}
|
||||
);
|
||||
new String[] {"__OTHER_THREADS", "thread3"},
|
||||
new int[] {1000, 2000},
|
||||
new int[][] {{10, 100}, {0, 300}});
|
||||
}
|
||||
|
||||
private void setupDirectory(Path processPath, int[] threadIds, String processName,
|
||||
@@ -289,8 +213,7 @@ public class KernelCpuThreadReaderTest {
|
||||
final OutputStream timeInStateStream =
|
||||
Files.newOutputStream(threadPath.resolve("time_in_state"));
|
||||
for (int j = 0; j < cpuFrequencies.length; j++) {
|
||||
final String line = String.valueOf(cpuFrequencies[j]) + " "
|
||||
+ String.valueOf(cpuTimes[i][j]) + "\n";
|
||||
final String line = cpuFrequencies[j] + " " + cpuTimes[i][j] + "\n";
|
||||
timeInStateStream.write(line.getBytes());
|
||||
}
|
||||
timeInStateStream.close();
|
||||
|
||||
@@ -1721,7 +1721,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
|
||||
throw new IllegalStateException("mKernelCpuThreadReader is null");
|
||||
}
|
||||
ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages =
|
||||
this.mKernelCpuThreadReader.getProcessCpuUsageByUids();
|
||||
this.mKernelCpuThreadReader.getProcessCpuUsage();
|
||||
if (processCpuUsages == null) {
|
||||
throw new IllegalStateException("processCpuUsages is null");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user