blob: f8c0d9e4a27eea7cb4f1c837a9eb7c95d5d2512d [file] [log] [blame]
Misha Wagner4b32c9f2019-01-25 15:30:14 +00001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.os;
18
19import android.annotation.Nullable;
20import android.content.Context;
21import android.database.ContentObserver;
22import android.net.Uri;
23import android.os.UserHandle;
24import android.provider.Settings;
25import android.util.KeyValueListParser;
26import android.util.Range;
27import android.util.Slog;
28
29import com.android.internal.annotations.VisibleForTesting;
30
31import java.util.ArrayList;
32import java.util.List;
33import java.util.function.Predicate;
34import java.util.regex.Matcher;
35import java.util.regex.Pattern;
36
37/**
38 * Service that handles settings for {@link KernelCpuThreadReader}
39 *
40 * <p>N.B.: The `collected_uids` setting takes a string representation of what UIDs to collect data
41 * for. A string representation is used as we will want to express UID ranges, therefore an integer
42 * array could not be used. The format of the string representation is detailed here: {@link
43 * UidPredicate#fromString}.
44 *
45 * @hide Only for use within the system server
46 */
47public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {
48 private static final String TAG = "KernelCpuThreadReaderSettingsObserver";
49
Misha Wagner3989eb02019-03-19 11:05:37 +000050 /** The number of frequency buckets to report */
Misha Wagner4b32c9f2019-01-25 15:30:14 +000051 private static final String NUM_BUCKETS_SETTINGS_KEY = "num_buckets";
Misha Wagner3989eb02019-03-19 11:05:37 +000052
Misha Wagner4b32c9f2019-01-25 15:30:14 +000053 private static final int NUM_BUCKETS_DEFAULT = 8;
54
Misha Wagner3989eb02019-03-19 11:05:37 +000055 /** List of UIDs to report data for */
Misha Wagner4b32c9f2019-01-25 15:30:14 +000056 private static final String COLLECTED_UIDS_SETTINGS_KEY = "collected_uids";
Misha Wagner3989eb02019-03-19 11:05:37 +000057
Misha Wagnerd250dd82019-03-01 16:06:28 +000058 private static final String COLLECTED_UIDS_DEFAULT = "0-0;1000-1000";
Misha Wagner4b32c9f2019-01-25 15:30:14 +000059
Misha Wagner3989eb02019-03-19 11:05:37 +000060 /** Minimum total CPU usage to report */
Misha Wagner648d2032019-02-11 10:43:27 +000061 private static final String MINIMUM_TOTAL_CPU_USAGE_MILLIS_SETTINGS_KEY =
62 "minimum_total_cpu_usage_millis";
Misha Wagner3989eb02019-03-19 11:05:37 +000063
Misha Wagnerd250dd82019-03-01 16:06:28 +000064 private static final int MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT = 10000;
Misha Wagner648d2032019-02-11 10:43:27 +000065
Misha Wagner4b32c9f2019-01-25 15:30:14 +000066 private final Context mContext;
67
Misha Wagner3989eb02019-03-19 11:05:37 +000068 @Nullable private final KernelCpuThreadReader mKernelCpuThreadReader;
Misha Wagner4b32c9f2019-01-25 15:30:14 +000069
Misha Wagner2487d3572019-03-18 17:30:14 +000070 @Nullable private final KernelCpuThreadReaderDiff mKernelCpuThreadReaderDiff;
71
Misha Wagner4b32c9f2019-01-25 15:30:14 +000072 /**
Misha Wagner3989eb02019-03-19 11:05:37 +000073 * @return returns a created {@link KernelCpuThreadReader} that will be modified by any change
74 * in settings, returns null if creation failed
Misha Wagner4b32c9f2019-01-25 15:30:14 +000075 */
76 @Nullable
Misha Wagner2487d3572019-03-18 17:30:14 +000077 public static KernelCpuThreadReaderDiff getSettingsModifiedReader(Context context) {
Misha Wagner4b32c9f2019-01-25 15:30:14 +000078 // Create the observer
79 KernelCpuThreadReaderSettingsObserver settingsObserver =
80 new KernelCpuThreadReaderSettingsObserver(context);
81 // Register the observer to listen for setting changes
Misha Wagner3989eb02019-03-19 11:05:37 +000082 Uri settingsUri = Settings.Global.getUriFor(Settings.Global.KERNEL_CPU_THREAD_READER);
83 context.getContentResolver()
84 .registerContentObserver(
85 settingsUri, false, settingsObserver, UserHandle.USER_SYSTEM);
Misha Wagner4b32c9f2019-01-25 15:30:14 +000086 // Return the observer's reader
Misha Wagner2487d3572019-03-18 17:30:14 +000087 return settingsObserver.mKernelCpuThreadReaderDiff;
Misha Wagner4b32c9f2019-01-25 15:30:14 +000088 }
89
90 private KernelCpuThreadReaderSettingsObserver(Context context) {
91 super(BackgroundThread.getHandler());
92 mContext = context;
Misha Wagner3989eb02019-03-19 11:05:37 +000093 mKernelCpuThreadReader =
94 KernelCpuThreadReader.create(
Misha Wagner2487d3572019-03-18 17:30:14 +000095 NUM_BUCKETS_DEFAULT, UidPredicate.fromString(COLLECTED_UIDS_DEFAULT));
96 mKernelCpuThreadReaderDiff =
97 new KernelCpuThreadReaderDiff(
98 mKernelCpuThreadReader, MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT);
Misha Wagner4b32c9f2019-01-25 15:30:14 +000099 }
100
101 @Override
102 public void onChange(boolean selfChange, Uri uri, int userId) {
103 updateReader();
104 }
105
Misha Wagner3989eb02019-03-19 11:05:37 +0000106 /** Update the reader with new settings */
Misha Wagner4b32c9f2019-01-25 15:30:14 +0000107 private void updateReader() {
108 if (mKernelCpuThreadReader == null) {
109 return;
110 }
111
112 final KeyValueListParser parser = new KeyValueListParser(',');
113 try {
Misha Wagner3989eb02019-03-19 11:05:37 +0000114 parser.setString(
115 Settings.Global.getString(
116 mContext.getContentResolver(),
117 Settings.Global.KERNEL_CPU_THREAD_READER));
Misha Wagner4b32c9f2019-01-25 15:30:14 +0000118 } catch (IllegalArgumentException e) {
119 Slog.e(TAG, "Bad settings", e);
120 return;
121 }
122
123 final UidPredicate uidPredicate;
124 try {
Misha Wagner3989eb02019-03-19 11:05:37 +0000125 uidPredicate =
126 UidPredicate.fromString(
127 parser.getString(COLLECTED_UIDS_SETTINGS_KEY, COLLECTED_UIDS_DEFAULT));
Misha Wagner4b32c9f2019-01-25 15:30:14 +0000128 } catch (NumberFormatException e) {
129 Slog.w(TAG, "Failed to get UID predicate", e);
130 return;
131 }
132
133 mKernelCpuThreadReader.setNumBuckets(
134 parser.getInt(NUM_BUCKETS_SETTINGS_KEY, NUM_BUCKETS_DEFAULT));
135 mKernelCpuThreadReader.setUidPredicate(uidPredicate);
Misha Wagner2487d3572019-03-18 17:30:14 +0000136 mKernelCpuThreadReaderDiff.setMinimumTotalCpuUsageMillis(
Misha Wagner3989eb02019-03-19 11:05:37 +0000137 parser.getInt(
138 MINIMUM_TOTAL_CPU_USAGE_MILLIS_SETTINGS_KEY,
139 MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT));
Misha Wagner4b32c9f2019-01-25 15:30:14 +0000140 }
141
Misha Wagner3989eb02019-03-19 11:05:37 +0000142 /** Check whether a UID belongs to a set of UIDs */
Misha Wagner4b32c9f2019-01-25 15:30:14 +0000143 @VisibleForTesting
144 public static class UidPredicate implements Predicate<Integer> {
145 private static final Pattern UID_RANGE_PATTERN = Pattern.compile("([0-9]+)-([0-9]+)");
146 private static final String UID_SPECIFIER_DELIMITER = ";";
147 private final List<Range<Integer>> mAcceptedUidRanges;
148
149 /**
150 * Create a UID predicate from a string representing a list of UID ranges
151 *
152 * <p>UID ranges are a pair of integers separated by a '-'. If you want to specify a single
Misha Wagner3989eb02019-03-19 11:05:37 +0000153 * UID (e.g. UID 1000), you can use {@code 1000-1000}. Lists of ranges are separated by a
154 * single ';'. For example, this would be a valid string representation: {@code
Misha Wagner4b32c9f2019-01-25 15:30:14 +0000155 * "1000-1999;2003-2003;2004-2004;2050-2060"}.
156 *
157 * <p>We do not use ',' to delimit as it is already used in separating different setting
158 * arguments.
159 *
Misha Wagner3989eb02019-03-19 11:05:37 +0000160 * @throws NumberFormatException if the input string is incorrectly formatted
Misha Wagner4b32c9f2019-01-25 15:30:14 +0000161 * @throws IllegalArgumentException if an UID range has a lower end than start
162 */
163 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
164 public static UidPredicate fromString(String predicateString) throws NumberFormatException {
165 final List<Range<Integer>> acceptedUidRanges = new ArrayList<>();
166 for (String uidSpecifier : predicateString.split(UID_SPECIFIER_DELIMITER)) {
167 final Matcher uidRangeMatcher = UID_RANGE_PATTERN.matcher(uidSpecifier);
168 if (!uidRangeMatcher.matches()) {
169 throw new NumberFormatException(
170 "Failed to recognize as number range: " + uidSpecifier);
171 }
Misha Wagner3989eb02019-03-19 11:05:37 +0000172 acceptedUidRanges.add(
173 Range.create(
174 Integer.parseInt(uidRangeMatcher.group(1)),
175 Integer.parseInt(uidRangeMatcher.group(2))));
Misha Wagner4b32c9f2019-01-25 15:30:14 +0000176 }
177 return new UidPredicate(acceptedUidRanges);
178 }
179
180 private UidPredicate(List<Range<Integer>> acceptedUidRanges) {
181 mAcceptedUidRanges = acceptedUidRanges;
182 }
183
184 @Override
Misha Wagner3989eb02019-03-19 11:05:37 +0000185 @SuppressWarnings("ForLoopReplaceableByForEach")
Misha Wagner4b32c9f2019-01-25 15:30:14 +0000186 public boolean test(Integer uid) {
187 for (int i = 0; i < mAcceptedUidRanges.size(); i++) {
188 if (mAcceptedUidRanges.get(i).contains(uid)) {
189 return true;
190 }
191 }
192 return false;
193 }
194 }
195}