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:
Misha Wagner
2019-03-19 11:05:37 +00:00
parent f7a0b28443
commit 3989eb02bc
6 changed files with 192 additions and 331 deletions

View File

@@ -47,7 +47,7 @@ public class KernelCpuThreadReaderPerfTest {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
assertNotNull(mKernelCpuThreadReader);
while (state.keepRunning()) {
this.mKernelCpuThreadReader.getCurrentProcessCpuUsage();
this.mKernelCpuThreadReader.getProcessCpuUsage();
}
}
}

View File

@@ -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);
}

View File

@@ -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)) {

View File

@@ -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());

View File

@@ -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();

View File

@@ -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");
}