Merge "Add settings for KernelCpuThreadReader"
diff --git a/apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java b/apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java
index 11c7599..9234849 100644
--- a/apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java
@@ -39,7 +39,8 @@
@Rule
public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- private final KernelCpuThreadReader mKernelCpuThreadReader = KernelCpuThreadReader.create();
+ private final KernelCpuThreadReader mKernelCpuThreadReader =
+ KernelCpuThreadReader.create(8, uid -> 1000 <= uid && uid < 2000);
@Test
public void timeReadCurrentProcessCpuUsage() {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a0a5921..e14bb66 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -14328,6 +14328,19 @@
public static final String LOOPER_STATS = "looper_stats";
/**
+ * Settings for collecting statistics on CPU usage per thread
+ *
+ * The following strings are supported as keys:
+ * <pre>
+ * num_buckets (int)
+ * collected_uids (string)
+ * </pre>
+ *
+ * @hide
+ */
+ public static final String KERNEL_CPU_THREAD_READER = "kernel_cpu_thread_reader";
+
+ /**
* Default user id to boot into. They map to user ids, for example, 10, 11, 12.
*
* @hide
diff --git a/core/java/com/android/internal/os/KernelCpuThreadReader.java b/core/java/com/android/internal/os/KernelCpuThreadReader.java
index 2742b7c..b1328e8 100644
--- a/core/java/com/android/internal/os/KernelCpuThreadReader.java
+++ b/core/java/com/android/internal/os/KernelCpuThreadReader.java
@@ -37,9 +37,10 @@
* frequency band.
*
* <p>Frequencies are bucketed together to reduce the amount of data created. This means that we
- * return {@link #NUM_BUCKETS} frequencies instead of the full number. 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
@@ -97,22 +98,17 @@
DEFAULT_PROC_PATH.resolve("self/time_in_state");
/**
- * Number of frequency buckets
- */
- private static final int NUM_BUCKETS = 8;
-
- /**
- * Default predicate for what UIDs to check for when getting processes. This filters to only
- * select UID 1000 (the {@code system} user)
- */
- private static final Predicate<Integer> DEFAULT_UID_PREDICATE = uid -> uid == 1000;
-
- /**
* Value returned when there was an error getting an integer ID value (e.g. PID, UID)
*/
private static final int ID_ERROR = -1;
/**
+ * 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;
+
+ /**
* Where the proc filesystem is mounted
*/
private final Path mProcPath;
@@ -121,7 +117,7 @@
* Frequencies read from the {@code time_in_state} file. Read from {@link
* #mProcTimeInStateReader#getCpuFrequenciesKhz()} and cast to {@code int[]}
*/
- private final int[] mFrequenciesKhz;
+ private int[] mFrequenciesKhz;
/**
* Used to read and parse {@code time_in_state} files
@@ -131,17 +127,10 @@
/**
* Used to sort frequencies and usage times into buckets
*/
- private final FrequencyBucketCreator mFrequencyBucketCreator;
+ private FrequencyBucketCreator mFrequencyBucketCreator;
private final Injector mInjector;
- private KernelCpuThreadReader() throws IOException {
- this(
- DEFAULT_PROC_PATH,
- DEFAULT_INITIAL_TIME_IN_STATE_PATH,
- new Injector());
- }
-
/**
* Create with a path where `proc` is mounted. Used primarily for testing
*
@@ -151,17 +140,16 @@
*/
@VisibleForTesting
public KernelCpuThreadReader(
+ int numBuckets,
+ Predicate<Integer> uidPredicate,
Path procPath,
Path initialTimeInStatePath,
Injector injector) throws IOException {
+ mUidPredicate = uidPredicate;
mProcPath = procPath;
mProcTimeInStateReader = new ProcTimeInStateReader(initialTimeInStatePath);
mInjector = injector;
-
- // Copy mProcTimeInState's frequencies and initialize bucketing
- final long[] frequenciesKhz = mProcTimeInStateReader.getFrequenciesKhz();
- mFrequencyBucketCreator = new FrequencyBucketCreator(frequenciesKhz, NUM_BUCKETS);
- mFrequenciesKhz = mFrequencyBucketCreator.getBucketMinFrequencies(frequenciesKhz);
+ setNumBuckets(numBuckets);
}
/**
@@ -170,9 +158,14 @@
* @return the reader, null if an exception was thrown during creation
*/
@Nullable
- public static KernelCpuThreadReader create() {
+ public static KernelCpuThreadReader create(int numBuckets, Predicate<Integer> uidPredicate) {
try {
- return new KernelCpuThreadReader();
+ return new KernelCpuThreadReader(
+ numBuckets,
+ uidPredicate,
+ DEFAULT_PROC_PATH,
+ DEFAULT_INITIAL_TIME_IN_STATE_PATH,
+ new Injector());
} catch (IOException e) {
Slog.e(TAG, "Failed to initialize KernelCpuThreadReader", e);
return null;
@@ -180,14 +173,6 @@
}
/**
- * Get the per-thread CPU usage of all processes belonging to UIDs between {@code [1000, 2000)}
- */
- @Nullable
- public ArrayList<ProcessCpuUsage> getProcessCpuUsageByUids() {
- return getProcessCpuUsageByUids(DEFAULT_UID_PREDICATE);
- }
-
- /**
* Get the per-thread CPU usage of all processes belonging to a set of UIDs
*
* <p>This function will crawl through all process {@code proc} directories found by the pattern
@@ -195,10 +180,11 @@
* approximately 500ms on a Pixel 2. Therefore, this method can be computationally expensive,
* and should not be called more than once an hour.
*
- * @param uidPredicate only get usage from processes owned by UIDs that match this predicate
+ * <p>Data is only collected for UIDs passing the predicate supplied in {@link
+ * #setUidPredicate}.
*/
@Nullable
- public ArrayList<ProcessCpuUsage> getProcessCpuUsageByUids(Predicate<Integer> uidPredicate) {
+ public ArrayList<ProcessCpuUsage> getProcessCpuUsageByUids() {
if (DEBUG) {
Slog.d(TAG, "Reading CPU thread usages for processes owned by UIDs");
}
@@ -213,7 +199,7 @@
if (uid == ID_ERROR || processId == ID_ERROR) {
continue;
}
- if (!uidPredicate.test(uid)) {
+ if (!mUidPredicate.test(uid)) {
continue;
}
@@ -247,10 +233,7 @@
*/
@Nullable
public ProcessCpuUsage getCurrentProcessCpuUsage() {
- return getProcessCpuUsage(
- mProcPath.resolve("self"),
- mInjector.myPid(),
- mInjector.myUid());
+ return getProcessCpuUsage(mProcPath.resolve("self"), mInjector.myPid(), mInjector.myUid());
}
/**
@@ -300,6 +283,31 @@
}
/**
+ * 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;
+ }
+
+ /**
* Get the CPU frequencies that correspond to the times reported in
* {@link ThreadCpuUsage#usageTimesMillis}
*/
diff --git a/core/java/com/android/internal/os/KernelCpuThreadReaderSettingsObserver.java b/core/java/com/android/internal/os/KernelCpuThreadReaderSettingsObserver.java
new file mode 100644
index 0000000..77f6a17
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelCpuThreadReaderSettingsObserver.java
@@ -0,0 +1,182 @@
+/*
+ * 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.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.KeyValueListParser;
+import android.util.Range;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Service that handles settings for {@link KernelCpuThreadReader}
+ *
+ * <p>N.B.: The `collected_uids` setting takes a string representation of what UIDs to collect data
+ * for. A string representation is used as we will want to express UID ranges, therefore an integer
+ * array could not be used. The format of the string representation is detailed here: {@link
+ * UidPredicate#fromString}.
+ *
+ * @hide Only for use within the system server
+ */
+public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {
+ private static final String TAG = "KernelCpuThreadReaderSettingsObserver";
+
+ /**
+ * 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
+ */
+ private static final String COLLECTED_UIDS_SETTINGS_KEY = "collected_uids";
+ private static final String COLLECTED_UIDS_DEFAULT = "1000-1000";
+
+ private final Context mContext;
+
+ @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
+ */
+ @Nullable
+ public static KernelCpuThreadReader getSettingsModifiedReader(Context context) {
+ // Create the observer
+ 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);
+ // Return the observer's reader
+ return settingsObserver.mKernelCpuThreadReader;
+ }
+
+ private KernelCpuThreadReaderSettingsObserver(Context context) {
+ super(BackgroundThread.getHandler());
+ mContext = context;
+ mKernelCpuThreadReader = KernelCpuThreadReader.create(
+ NUM_BUCKETS_DEFAULT,
+ UidPredicate.fromString(COLLECTED_UIDS_DEFAULT));
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri, int userId) {
+ updateReader();
+ }
+
+ /**
+ * Update the reader with new settings
+ */
+ private void updateReader() {
+ if (mKernelCpuThreadReader == null) {
+ return;
+ }
+
+ final KeyValueListParser parser = new KeyValueListParser(',');
+ try {
+ parser.setString(Settings.Global.getString(
+ mContext.getContentResolver(), Settings.Global.KERNEL_CPU_THREAD_READER));
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Bad settings", e);
+ return;
+ }
+
+ final UidPredicate uidPredicate;
+ try {
+ 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;
+ }
+
+ mKernelCpuThreadReader.setNumBuckets(
+ parser.getInt(NUM_BUCKETS_SETTINGS_KEY, NUM_BUCKETS_DEFAULT));
+ mKernelCpuThreadReader.setUidPredicate(uidPredicate);
+ }
+
+ /**
+ * 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]+)");
+ private static final String UID_SPECIFIER_DELIMITER = ";";
+ private final List<Range<Integer>> mAcceptedUidRanges;
+
+ /**
+ * 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
+ * "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 IllegalArgumentException if an UID range has a lower end than start
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public static UidPredicate fromString(String predicateString) throws NumberFormatException {
+ final List<Range<Integer>> acceptedUidRanges = new ArrayList<>();
+ for (String uidSpecifier : predicateString.split(UID_SPECIFIER_DELIMITER)) {
+ final Matcher uidRangeMatcher = UID_RANGE_PATTERN.matcher(uidSpecifier);
+ if (!uidRangeMatcher.matches()) {
+ throw new NumberFormatException(
+ "Failed to recognize as number range: " + uidSpecifier);
+ }
+ acceptedUidRanges.add(Range.create(
+ Integer.parseInt(uidRangeMatcher.group(1)),
+ Integer.parseInt(uidRangeMatcher.group(2))));
+ }
+ return new UidPredicate(acceptedUidRanges);
+ }
+
+ private UidPredicate(List<Range<Integer>> acceptedUidRanges) {
+ mAcceptedUidRanges = acceptedUidRanges;
+ }
+
+ @Override
+ public boolean test(Integer uid) {
+ for (int i = 0; i < mAcceptedUidRanges.size(); i++) {
+ if (mAcceptedUidRanges.get(i).contains(uid)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index f420033..2cb925a 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -297,6 +297,7 @@
Settings.Global.INTENT_FIREWALL_UPDATE_METADATA_URL,
Settings.Global.JOB_SCHEDULER_CONSTANTS,
Settings.Global.KEEP_PROFILE_IN_BACKGROUND,
+ Settings.Global.KERNEL_CPU_THREAD_READER,
Settings.Global.LANG_ID_UPDATE_CONTENT_URL,
Settings.Global.LANG_ID_UPDATE_METADATA_URL,
Settings.Global.LAST_ACTIVE_USER_ID,
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderEndToEndTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderEndToEndTest.java
index c03d1f3..3ddd8aa 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderEndToEndTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderEndToEndTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import android.os.Process;
import android.os.SystemClock;
import android.support.test.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
@@ -121,7 +122,8 @@
workFinishedLatch.await();
// Get thread data from KernelCpuThreadReader
- final KernelCpuThreadReader kernelCpuThreadReader = KernelCpuThreadReader.create();
+ final KernelCpuThreadReader kernelCpuThreadReader =
+ KernelCpuThreadReader.create(8, uid -> uid == Process.myUid());
assertNotNull(kernelCpuThreadReader);
final ProcessCpuUsage currentProcessCpuUsage =
kernelCpuThreadReader.getCurrentProcessCpuUsage();
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
index 0c56b8a..b9744f5 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
@@ -18,8 +18,10 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertThrows;
import android.content.Context;
import android.os.FileUtils;
@@ -99,6 +101,8 @@
THREAD_NAMES, THREAD_CPU_FREQUENCIES, THREAD_CPU_TIMES);
final KernelCpuThreadReader kernelCpuThreadReader = new KernelCpuThreadReader(
+ 8,
+ uid -> 1000 <= uid && uid < 2000,
mProcDirectory.toPath(),
mProcDirectory.toPath().resolve("self/task/" + THREAD_IDS[0] + "/time_in_state"),
processUtils);
@@ -138,11 +142,13 @@
new int[][]{{uid}});
}
final KernelCpuThreadReader kernelCpuThreadReader = new KernelCpuThreadReader(
+ 8,
+ uidPredicate,
mProcDirectory.toPath(),
mProcDirectory.toPath().resolve(uids[0] + "/task/" + uids[0] + "/time_in_state"),
processUtils);
ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsageByUids =
- kernelCpuThreadReader.getProcessCpuUsageByUids(uidPredicate);
+ kernelCpuThreadReader.getProcessCpuUsageByUids();
processCpuUsageByUids.sort(Comparator.comparing(usage -> usage.processId));
assertEquals(expectedUids.length, processCpuUsageByUids.size());
@@ -350,4 +356,106 @@
4, KernelCpuThreadReader.FrequencyBucketCreator.getBigFrequenciesStartIndex(
new long[]{1, 2, 3, 4}));
}
+
+ @Test
+ public void testUidPredicate_singleRange() {
+ KernelCpuThreadReaderSettingsObserver.UidPredicate uidPredicate =
+ KernelCpuThreadReaderSettingsObserver.UidPredicate.fromString("1000-1999");
+ assertTrue(uidPredicate.test(1000));
+ assertTrue(uidPredicate.test(1050));
+ assertTrue(uidPredicate.test(1999));
+ assertFalse(uidPredicate.test(2000));
+ assertFalse(uidPredicate.test(0));
+ assertFalse(uidPredicate.test(10000));
+ assertFalse(uidPredicate.test(-100));
+ }
+
+ @Test
+ public void testUidPredicate_singleUid() {
+ KernelCpuThreadReaderSettingsObserver.UidPredicate uidPredicate =
+ KernelCpuThreadReaderSettingsObserver.UidPredicate.fromString("1234-1234");
+ assertTrue(uidPredicate.test(1234));
+ assertFalse(uidPredicate.test(1235));
+ assertFalse(uidPredicate.test(1232));
+ assertFalse(uidPredicate.test(0));
+ assertFalse(uidPredicate.test(-1234));
+ }
+
+ @Test
+ public void testUidPredicate_uidAndRange() {
+ KernelCpuThreadReaderSettingsObserver.UidPredicate uidPredicate =
+ KernelCpuThreadReaderSettingsObserver.UidPredicate.fromString(
+ "1000-1000;1050-1060");
+ assertTrue(uidPredicate.test(1000));
+ assertTrue(uidPredicate.test(1050));
+ assertTrue(uidPredicate.test(1054));
+ assertTrue(uidPredicate.test(1060));
+ assertFalse(uidPredicate.test(1040));
+ assertFalse(uidPredicate.test(1001));
+ assertFalse(uidPredicate.test(0));
+ assertFalse(uidPredicate.test(-1000));
+ }
+
+ @Test
+ public void testUidPredicate_multiple() {
+ KernelCpuThreadReaderSettingsObserver.UidPredicate uidPredicate =
+ KernelCpuThreadReaderSettingsObserver.UidPredicate.fromString(
+ "1000-1000;1050-1060;1001-1001;2000-3000");
+ assertTrue(uidPredicate.test(1000));
+ assertTrue(uidPredicate.test(1001));
+ assertTrue(uidPredicate.test(1050));
+ assertTrue(uidPredicate.test(1054));
+ assertTrue(uidPredicate.test(1060));
+ assertTrue(uidPredicate.test(1001));
+ assertTrue(uidPredicate.test(2000));
+ assertTrue(uidPredicate.test(2444));
+ assertTrue(uidPredicate.test(3000));
+ assertFalse(uidPredicate.test(0));
+ assertFalse(uidPredicate.test(1040));
+ assertFalse(uidPredicate.test(3001));
+ assertFalse(uidPredicate.test(1999));
+ }
+
+ @Test
+ public void testUidPredicate_emptyRangeString() {
+ assertThrows(
+ NumberFormatException.class,
+ () -> KernelCpuThreadReaderSettingsObserver.UidPredicate.fromString(""));
+ }
+
+ @Test
+ public void testUidPredicate_singleNumber() {
+ assertThrows(
+ NumberFormatException.class,
+ () -> KernelCpuThreadReaderSettingsObserver.UidPredicate.fromString("1000"));
+ }
+
+ @Test
+ public void testUidPredicate_lettersInRange() {
+ assertThrows(
+ NumberFormatException.class,
+ () -> KernelCpuThreadReaderSettingsObserver.UidPredicate.fromString(
+ "0-0;1-1;a;3-3"));
+ }
+
+ @Test
+ public void testUidPredicate_onlyLetters() {
+ assertThrows(
+ NumberFormatException.class,
+ () -> KernelCpuThreadReaderSettingsObserver.UidPredicate.fromString("abc"));
+ }
+
+ @Test
+ public void testUidPredicate_backwardsRange() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> KernelCpuThreadReaderSettingsObserver.UidPredicate.fromString("20-10"));
+ }
+
+ @Test
+ public void testUidPredicate_comma() {
+ assertThrows(
+ NumberFormatException.class,
+ () -> KernelCpuThreadReaderSettingsObserver.UidPredicate.fromString("1-1,2-2,3-3"));
+ }
}
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 2be78fe3..2378c57 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -98,6 +98,7 @@
import com.android.internal.os.BinderCallsStats.ExportedCallStat;
import com.android.internal.os.KernelCpuSpeedReader;
import com.android.internal.os.KernelCpuThreadReader;
+import com.android.internal.os.KernelCpuThreadReaderSettingsObserver;
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
@@ -203,7 +204,7 @@
"zygote64",
};
- private static final int CPU_TIME_PER_THREAD_FREQ_NUM_FREQUENCIES = 8;
+ private static final int CPU_TIME_PER_THREAD_FREQ_MAX_NUM_FREQUENCIES = 8;
static final class CompanionHandler extends Handler {
CompanionHandler(Looper looper) {
@@ -332,7 +333,8 @@
handlerThread.start();
mHandler = new CompanionHandler(handlerThread.getLooper());
- mKernelCpuThreadReader = KernelCpuThreadReader.create();
+ mKernelCpuThreadReader =
+ KernelCpuThreadReaderSettingsObserver.getSettingsModifiedReader(mContext);
}
@Override
@@ -1678,8 +1680,8 @@
return;
}
int[] cpuFrequencies = mKernelCpuThreadReader.getCpuFrequenciesKhz();
- if (cpuFrequencies.length != CPU_TIME_PER_THREAD_FREQ_NUM_FREQUENCIES) {
- Slog.w(TAG, "Expected " + CPU_TIME_PER_THREAD_FREQ_NUM_FREQUENCIES
+ if (cpuFrequencies.length > CPU_TIME_PER_THREAD_FREQ_MAX_NUM_FREQUENCIES) {
+ Slog.w(TAG, "Expected maximum " + CPU_TIME_PER_THREAD_FREQ_MAX_NUM_FREQUENCIES
+ " frequencies, but got " + cpuFrequencies.length);
return;
}
@@ -1703,9 +1705,17 @@
e.writeInt(threadCpuUsage.threadId);
e.writeString(processCpuUsage.processName);
e.writeString(threadCpuUsage.threadName);
- for (int k = 0; k < CPU_TIME_PER_THREAD_FREQ_NUM_FREQUENCIES; k++) {
- e.writeInt(cpuFrequencies[k]);
- e.writeInt(threadCpuUsage.usageTimesMillis[k]);
+ for (int k = 0; k < CPU_TIME_PER_THREAD_FREQ_MAX_NUM_FREQUENCIES; k++) {
+ if (k < cpuFrequencies.length) {
+ e.writeInt(cpuFrequencies[k]);
+ e.writeInt(threadCpuUsage.usageTimesMillis[k]);
+ } else {
+ // If we have no more frequencies to write, we still must write empty data.
+ // We know that this data is empty (and not just zero) because all
+ // frequencies are expected to be greater than zero
+ e.writeInt(0);
+ e.writeInt(0);
+ }
}
pulledData.add(e);
}