blob: 718bcb43bd9ab4203e00eee03e083b6e749b6633 [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
50 /**
51 * The number of frequency buckets to report
52 */
53 private static final String NUM_BUCKETS_SETTINGS_KEY = "num_buckets";
54 private static final int NUM_BUCKETS_DEFAULT = 8;
55
56 /**
57 * List of UIDs to report data for
58 */
59 private static final String COLLECTED_UIDS_SETTINGS_KEY = "collected_uids";
60 private static final String COLLECTED_UIDS_DEFAULT = "1000-1000";
61
Misha Wagner648d2032019-02-11 10:43:27 +000062 /**
63 * Minimum total CPU usage to report
64 */
65 private static final String MINIMUM_TOTAL_CPU_USAGE_MILLIS_SETTINGS_KEY =
66 "minimum_total_cpu_usage_millis";
67 private static final int MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT = 0;
68
Misha Wagner4b32c9f2019-01-25 15:30:14 +000069 private final Context mContext;
70
71 @Nullable
72 private final KernelCpuThreadReader mKernelCpuThreadReader;
73
74 /**
75 * @return returns a created {@link KernelCpuThreadReader} that will be modified by any
76 * change in settings, returns null if creation failed
77 */
78 @Nullable
79 public static KernelCpuThreadReader getSettingsModifiedReader(Context context) {
80 // Create the observer
81 KernelCpuThreadReaderSettingsObserver settingsObserver =
82 new KernelCpuThreadReaderSettingsObserver(context);
83 // Register the observer to listen for setting changes
84 Uri settingsUri =
85 Settings.Global.getUriFor(Settings.Global.KERNEL_CPU_THREAD_READER);
86 context.getContentResolver().registerContentObserver(
87 settingsUri, false, settingsObserver, UserHandle.USER_SYSTEM);
88 // Return the observer's reader
89 return settingsObserver.mKernelCpuThreadReader;
90 }
91
92 private KernelCpuThreadReaderSettingsObserver(Context context) {
93 super(BackgroundThread.getHandler());
94 mContext = context;
95 mKernelCpuThreadReader = KernelCpuThreadReader.create(
96 NUM_BUCKETS_DEFAULT,
Misha Wagner648d2032019-02-11 10:43:27 +000097 UidPredicate.fromString(COLLECTED_UIDS_DEFAULT),
98 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
106 /**
107 * Update the reader with new settings
108 */
109 private void updateReader() {
110 if (mKernelCpuThreadReader == null) {
111 return;
112 }
113
114 final KeyValueListParser parser = new KeyValueListParser(',');
115 try {
116 parser.setString(Settings.Global.getString(
117 mContext.getContentResolver(), Settings.Global.KERNEL_CPU_THREAD_READER));
118 } catch (IllegalArgumentException e) {
119 Slog.e(TAG, "Bad settings", e);
120 return;
121 }
122
123 final UidPredicate uidPredicate;
124 try {
125 uidPredicate = UidPredicate.fromString(
126 parser.getString(COLLECTED_UIDS_SETTINGS_KEY, COLLECTED_UIDS_DEFAULT));
127 } catch (NumberFormatException e) {
128 Slog.w(TAG, "Failed to get UID predicate", e);
129 return;
130 }
131
132 mKernelCpuThreadReader.setNumBuckets(
133 parser.getInt(NUM_BUCKETS_SETTINGS_KEY, NUM_BUCKETS_DEFAULT));
134 mKernelCpuThreadReader.setUidPredicate(uidPredicate);
Misha Wagner648d2032019-02-11 10:43:27 +0000135 mKernelCpuThreadReader.setMinimumTotalCpuUsageMillis(parser.getInt(
136 MINIMUM_TOTAL_CPU_USAGE_MILLIS_SETTINGS_KEY,
137 MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT));
Misha Wagner4b32c9f2019-01-25 15:30:14 +0000138 }
139
140 /**
141 * Check whether a UID belongs to a set of UIDs
142 */
143 @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
153 * UID (e.g. UID 1000), you can use {@code 1000-1000}. Lists of ranges are separated by
154 * a single ';'. For example, this would be a valid string representation: {@code
155 * "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 *
160 * @throws NumberFormatException if the input string is incorrectly formatted
161 * @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 }
172 acceptedUidRanges.add(Range.create(
173 Integer.parseInt(uidRangeMatcher.group(1)),
174 Integer.parseInt(uidRangeMatcher.group(2))));
175 }
176 return new UidPredicate(acceptedUidRanges);
177 }
178
179 private UidPredicate(List<Range<Integer>> acceptedUidRanges) {
180 mAcceptedUidRanges = acceptedUidRanges;
181 }
182
183 @Override
184 public boolean test(Integer uid) {
185 for (int i = 0; i < mAcceptedUidRanges.size(); i++) {
186 if (mAcceptedUidRanges.get(i).contains(uid)) {
187 return true;
188 }
189 }
190 return false;
191 }
192 }
193}