1 /*
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 
17 package com.android.internal.os;
18 
19 import android.annotation.Nullable;
20 import android.content.Context;
21 import android.database.ContentObserver;
22 import android.net.Uri;
23 import android.os.UserHandle;
24 import android.provider.Settings;
25 import android.util.KeyValueListParser;
26 import android.util.Range;
27 import android.util.Slog;
28 
29 import com.android.internal.annotations.VisibleForTesting;
30 
31 import java.util.ArrayList;
32 import java.util.Collection;
33 import java.util.List;
34 import java.util.function.Predicate;
35 import java.util.regex.Matcher;
36 import java.util.regex.Pattern;
37 
38 /**
39  * Service that handles settings for {@link KernelCpuThreadReader}
40  *
41  * <p>N.B.: The `collected_uids` setting takes a string representation of what UIDs to collect data
42  * for. A string representation is used as we will want to express UID ranges, therefore an integer
43  * array could not be used. The format of the string representation is detailed here: {@link
44  * UidPredicate#fromString}.
45  *
46  * @hide Only for use within the system server
47  */
48 public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {
49     private static final String TAG = "KernelCpuThreadReaderSettingsObserver";
50 
51     /** The number of frequency buckets to report */
52     private static final String NUM_BUCKETS_SETTINGS_KEY = "num_buckets";
53 
54     private static final int NUM_BUCKETS_DEFAULT = 8;
55 
56     /** List of UIDs to report data for */
57     private static final String COLLECTED_UIDS_SETTINGS_KEY = "collected_uids";
58 
59     private static final String COLLECTED_UIDS_DEFAULT = "0-0;1000-1000";
60 
61     /** Minimum total CPU usage to report */
62     private static final String MINIMUM_TOTAL_CPU_USAGE_MILLIS_SETTINGS_KEY =
63             "minimum_total_cpu_usage_millis";
64 
65     private static final int MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT = 10000;
66 
67     private final Context mContext;
68 
69     @Nullable private final KernelCpuThreadReader mKernelCpuThreadReader;
70 
71     @Nullable private final KernelCpuThreadReaderDiff mKernelCpuThreadReaderDiff;
72 
73     /**
74      * @return returns a created {@link KernelCpuThreadReader} that will be modified by any change
75      *     in settings, returns null if creation failed
76      */
77     @Nullable
getSettingsModifiedReader(Context context)78     public static KernelCpuThreadReaderDiff getSettingsModifiedReader(Context context) {
79         // Create the observer
80         KernelCpuThreadReaderSettingsObserver settingsObserver =
81                 new KernelCpuThreadReaderSettingsObserver(context);
82         // Register the observer to listen for setting changes
83         Uri settingsUri = Settings.Global.getUriFor(Settings.Global.KERNEL_CPU_THREAD_READER);
84         context.getContentResolver()
85                 .registerContentObserver(
86                         settingsUri, false, settingsObserver, UserHandle.USER_SYSTEM);
87         // Return the observer's reader
88         return settingsObserver.mKernelCpuThreadReaderDiff;
89     }
90 
KernelCpuThreadReaderSettingsObserver(Context context)91     private KernelCpuThreadReaderSettingsObserver(Context context) {
92         super(BackgroundThread.getHandler());
93         mContext = context;
94         mKernelCpuThreadReader =
95                 KernelCpuThreadReader.create(
96                         NUM_BUCKETS_DEFAULT, UidPredicate.fromString(COLLECTED_UIDS_DEFAULT));
97         mKernelCpuThreadReaderDiff =
98                 mKernelCpuThreadReader == null
99                         ? null
100                         : new KernelCpuThreadReaderDiff(
101                                 mKernelCpuThreadReader, MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT);
102     }
103 
104     @Override
onChange(boolean selfChange, Collection<Uri> uris, int flags, int userId)105     public void onChange(boolean selfChange, Collection<Uri> uris, int flags, int userId) {
106         updateReader();
107     }
108 
109     /** Update the reader with new settings */
updateReader()110     private void updateReader() {
111         if (mKernelCpuThreadReader == null) {
112             return;
113         }
114 
115         final KeyValueListParser parser = new KeyValueListParser(',');
116         try {
117             parser.setString(
118                     Settings.Global.getString(
119                             mContext.getContentResolver(),
120                             Settings.Global.KERNEL_CPU_THREAD_READER));
121         } catch (IllegalArgumentException e) {
122             Slog.e(TAG, "Bad settings", e);
123             return;
124         }
125 
126         final UidPredicate uidPredicate;
127         try {
128             uidPredicate =
129                     UidPredicate.fromString(
130                             parser.getString(COLLECTED_UIDS_SETTINGS_KEY, COLLECTED_UIDS_DEFAULT));
131         } catch (NumberFormatException e) {
132             Slog.w(TAG, "Failed to get UID predicate", e);
133             return;
134         }
135 
136         mKernelCpuThreadReader.setNumBuckets(
137                 parser.getInt(NUM_BUCKETS_SETTINGS_KEY, NUM_BUCKETS_DEFAULT));
138         mKernelCpuThreadReader.setUidPredicate(uidPredicate);
139         mKernelCpuThreadReaderDiff.setMinimumTotalCpuUsageMillis(
140                 parser.getInt(
141                         MINIMUM_TOTAL_CPU_USAGE_MILLIS_SETTINGS_KEY,
142                         MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT));
143     }
144 
145     /** Check whether a UID belongs to a set of UIDs */
146     @VisibleForTesting
147     public static class UidPredicate implements Predicate<Integer> {
148         private static final Pattern UID_RANGE_PATTERN = Pattern.compile("([0-9]+)-([0-9]+)");
149         private static final String UID_SPECIFIER_DELIMITER = ";";
150         private final List<Range<Integer>> mAcceptedUidRanges;
151 
152         /**
153          * Create a UID predicate from a string representing a list of UID ranges
154          *
155          * <p>UID ranges are a pair of integers separated by a '-'. If you want to specify a single
156          * UID (e.g. UID 1000), you can use {@code 1000-1000}. Lists of ranges are separated by a
157          * single ';'. For example, this would be a valid string representation: {@code
158          * "1000-1999;2003-2003;2004-2004;2050-2060"}.
159          *
160          * <p>We do not use ',' to delimit as it is already used in separating different setting
161          * arguments.
162          *
163          * @throws NumberFormatException if the input string is incorrectly formatted
164          * @throws IllegalArgumentException if an UID range has a lower end than start
165          */
166         @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
fromString(String predicateString)167         public static UidPredicate fromString(String predicateString) throws NumberFormatException {
168             final List<Range<Integer>> acceptedUidRanges = new ArrayList<>();
169             for (String uidSpecifier : predicateString.split(UID_SPECIFIER_DELIMITER)) {
170                 final Matcher uidRangeMatcher = UID_RANGE_PATTERN.matcher(uidSpecifier);
171                 if (!uidRangeMatcher.matches()) {
172                     throw new NumberFormatException(
173                             "Failed to recognize as number range: " + uidSpecifier);
174                 }
175                 acceptedUidRanges.add(
176                         Range.create(
177                                 Integer.parseInt(uidRangeMatcher.group(1)),
178                                 Integer.parseInt(uidRangeMatcher.group(2))));
179             }
180             return new UidPredicate(acceptedUidRanges);
181         }
182 
UidPredicate(List<Range<Integer>> acceptedUidRanges)183         private UidPredicate(List<Range<Integer>> acceptedUidRanges) {
184             mAcceptedUidRanges = acceptedUidRanges;
185         }
186 
187         @Override
188         @SuppressWarnings("ForLoopReplaceableByForEach")
test(Integer uid)189         public boolean test(Integer uid) {
190             for (int i = 0; i < mAcceptedUidRanges.size(); i++) {
191                 if (mAcceptedUidRanges.get(i).contains(uid)) {
192                     return true;
193                 }
194             }
195             return false;
196         }
197     }
198 }
199