/* * 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.Collection; 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} * *

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 = "0-0;1000-1000"; /** 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 KernelCpuThreadReaderDiff mKernelCpuThreadReaderDiff; /** * @return returns a created {@link KernelCpuThreadReader} that will be modified by any change * in settings, returns null if creation failed */ @Nullable public static KernelCpuThreadReaderDiff 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.mKernelCpuThreadReaderDiff; } private KernelCpuThreadReaderSettingsObserver(Context context) { super(BackgroundThread.getHandler()); mContext = context; mKernelCpuThreadReader = KernelCpuThreadReader.create( NUM_BUCKETS_DEFAULT, UidPredicate.fromString(COLLECTED_UIDS_DEFAULT)); mKernelCpuThreadReaderDiff = mKernelCpuThreadReader == null ? null : new KernelCpuThreadReaderDiff( mKernelCpuThreadReader, MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT); } @Override public void onChange(boolean selfChange, Collection uris, int flags, 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); mKernelCpuThreadReaderDiff.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 */ @VisibleForTesting public static class UidPredicate implements Predicate { private static final Pattern UID_RANGE_PATTERN = Pattern.compile("([0-9]+)-([0-9]+)"); private static final String UID_SPECIFIER_DELIMITER = ";"; private final List> mAcceptedUidRanges; /** * Create a UID predicate from a string representing a list of UID ranges * *

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"}. * *

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> 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> acceptedUidRanges) { mAcceptedUidRanges = acceptedUidRanges; } @Override @SuppressWarnings("ForLoopReplaceableByForEach") public boolean test(Integer uid) { for (int i = 0; i < mAcceptedUidRanges.size(); i++) { if (mAcceptedUidRanges.get(i).contains(uid)) { return true; } } return false; } } }