blob: f8c0d9e4a27eea7cb4f1c837a9eb7c95d5d2512d [file] [log] [blame]
/*
* 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 = "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 =
new KernelCpuThreadReaderDiff(
mKernelCpuThreadReader, MINIMUM_TOTAL_CPU_USAGE_MILLIS_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);
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<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
@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;
}
}
}