Loading apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java +2 −1 Original line number Diff line number Diff line Loading @@ -39,7 +39,8 @@ public class KernelCpuThreadReaderPerfTest { @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() { Loading core/java/android/provider/Settings.java +13 −0 Original line number Diff line number Diff line Loading @@ -14327,6 +14327,19 @@ public final class Settings { */ 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. * core/java/com/android/internal/os/KernelCpuThreadReader.java +52 −44 Original line number Diff line number Diff line Loading @@ -37,9 +37,10 @@ import java.util.function.Predicate; * 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 Loading Loading @@ -97,20 +98,15 @@ public class KernelCpuThreadReader { DEFAULT_PROC_PATH.resolve("self/time_in_state"); /** * Number of frequency buckets * Value returned when there was an error getting an integer ID value (e.g. PID, UID) */ private static final int NUM_BUCKETS = 8; private static final int ID_ERROR = -1; /** * Default predicate for what UIDs to check for when getting processes. This filters to only * select UID 1000 (the {@code system} user) * When checking whether to report data for a thread, we check the UID of the thread's owner * against this predicate */ 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; private Predicate<Integer> mUidPredicate; /** * Where the proc filesystem is mounted Loading @@ -121,7 +117,7 @@ public class KernelCpuThreadReader { * 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 Loading @@ -131,17 +127,10 @@ public class KernelCpuThreadReader { /** * 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 * Loading @@ -151,17 +140,16 @@ public class KernelCpuThreadReader { */ @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); } /** Loading @@ -170,23 +158,20 @@ public class KernelCpuThreadReader { * @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; } } /** * 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 * Loading @@ -195,10 +180,11 @@ public class KernelCpuThreadReader { * 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"); } Loading @@ -213,7 +199,7 @@ public class KernelCpuThreadReader { if (uid == ID_ERROR || processId == ID_ERROR) { continue; } if (!uidPredicate.test(uid)) { if (!mUidPredicate.test(uid)) { continue; } Loading Loading @@ -247,10 +233,7 @@ public class KernelCpuThreadReader { */ @Nullable public ProcessCpuUsage getCurrentProcessCpuUsage() { return getProcessCpuUsage( mProcPath.resolve("self"), mInjector.myPid(), mInjector.myUid()); return getProcessCpuUsage(mProcPath.resolve("self"), mInjector.myPid(), mInjector.myUid()); } /** Loading Loading @@ -299,6 +282,31 @@ public class KernelCpuThreadReader { 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; } /** * Get the CPU frequencies that correspond to the times reported in * {@link ThreadCpuUsage#usageTimesMillis} Loading core/java/com/android/internal/os/KernelCpuThreadReaderSettingsObserver.java 0 → 100644 +182 −0 Original line number Diff line number Diff line /* * 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; } } } core/tests/coretests/src/android/provider/SettingsBackupTest.java +1 −0 Original line number Diff line number Diff line Loading @@ -297,6 +297,7 @@ public class SettingsBackupTest { 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, Loading Loading
apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java +2 −1 Original line number Diff line number Diff line Loading @@ -39,7 +39,8 @@ public class KernelCpuThreadReaderPerfTest { @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() { Loading
core/java/android/provider/Settings.java +13 −0 Original line number Diff line number Diff line Loading @@ -14327,6 +14327,19 @@ public final class Settings { */ 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. *
core/java/com/android/internal/os/KernelCpuThreadReader.java +52 −44 Original line number Diff line number Diff line Loading @@ -37,9 +37,10 @@ import java.util.function.Predicate; * 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 Loading Loading @@ -97,20 +98,15 @@ public class KernelCpuThreadReader { DEFAULT_PROC_PATH.resolve("self/time_in_state"); /** * Number of frequency buckets * Value returned when there was an error getting an integer ID value (e.g. PID, UID) */ private static final int NUM_BUCKETS = 8; private static final int ID_ERROR = -1; /** * Default predicate for what UIDs to check for when getting processes. This filters to only * select UID 1000 (the {@code system} user) * When checking whether to report data for a thread, we check the UID of the thread's owner * against this predicate */ 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; private Predicate<Integer> mUidPredicate; /** * Where the proc filesystem is mounted Loading @@ -121,7 +117,7 @@ public class KernelCpuThreadReader { * 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 Loading @@ -131,17 +127,10 @@ public class KernelCpuThreadReader { /** * 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 * Loading @@ -151,17 +140,16 @@ public class KernelCpuThreadReader { */ @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); } /** Loading @@ -170,23 +158,20 @@ public class KernelCpuThreadReader { * @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; } } /** * 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 * Loading @@ -195,10 +180,11 @@ public class KernelCpuThreadReader { * 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"); } Loading @@ -213,7 +199,7 @@ public class KernelCpuThreadReader { if (uid == ID_ERROR || processId == ID_ERROR) { continue; } if (!uidPredicate.test(uid)) { if (!mUidPredicate.test(uid)) { continue; } Loading Loading @@ -247,10 +233,7 @@ public class KernelCpuThreadReader { */ @Nullable public ProcessCpuUsage getCurrentProcessCpuUsage() { return getProcessCpuUsage( mProcPath.resolve("self"), mInjector.myPid(), mInjector.myUid()); return getProcessCpuUsage(mProcPath.resolve("self"), mInjector.myPid(), mInjector.myUid()); } /** Loading Loading @@ -299,6 +282,31 @@ public class KernelCpuThreadReader { 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; } /** * Get the CPU frequencies that correspond to the times reported in * {@link ThreadCpuUsage#usageTimesMillis} Loading
core/java/com/android/internal/os/KernelCpuThreadReaderSettingsObserver.java 0 → 100644 +182 −0 Original line number Diff line number Diff line /* * 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; } } }
core/tests/coretests/src/android/provider/SettingsBackupTest.java +1 −0 Original line number Diff line number Diff line Loading @@ -297,6 +297,7 @@ public class SettingsBackupTest { 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, Loading