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.List;
33 import java.util.function.Predicate;
34 import java.util.regex.Matcher;
35 import 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  */
47 public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {
48     private static final String TAG = "KernelCpuThreadReaderSettingsObserver";
49 
50     /** The number of frequency buckets to report */
51     private static final String NUM_BUCKETS_SETTINGS_KEY = "num_buckets";
52 
53     private static final int NUM_BUCKETS_DEFAULT = 8;
54 
55     /** List of UIDs to report data for */
56     private static final String COLLECTED_UIDS_SETTINGS_KEY = "collected_uids";
57 
58     private static final String COLLECTED_UIDS_DEFAULT = "0-0;1000-1000";
59 
60     /** Minimum total CPU usage to report */
61     private static final String MINIMUM_TOTAL_CPU_USAGE_MILLIS_SETTINGS_KEY =
62             "minimum_total_cpu_usage_millis";
63 
64     private static final int MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT = 10000;
65 
66     private final Context mContext;
67 
68     @Nullable private final KernelCpuThreadReader mKernelCpuThreadReader;
69 
70     @Nullable private final KernelCpuThreadReaderDiff mKernelCpuThreadReaderDiff;
71 
72     /**
73      * @return returns a created {@link KernelCpuThreadReader} that will be modified by any change
74      *     in settings, returns null if creation failed
75      */
76     @Nullable
getSettingsModifiedReader(Context context)77     public static KernelCpuThreadReaderDiff getSettingsModifiedReader(Context context) {
78         // Create the observer
79         KernelCpuThreadReaderSettingsObserver settingsObserver =
80                 new KernelCpuThreadReaderSettingsObserver(context);
81         // Register the observer to listen for setting changes
82         Uri settingsUri = Settings.Global.getUriFor(Settings.Global.KERNEL_CPU_THREAD_READER);
83         context.getContentResolver()
84                 .registerContentObserver(
85                         settingsUri, false, settingsObserver, UserHandle.USER_SYSTEM);
86         // Return the observer's reader
87         return settingsObserver.mKernelCpuThreadReaderDiff;
88     }
89 
KernelCpuThreadReaderSettingsObserver(Context context)90     private KernelCpuThreadReaderSettingsObserver(Context context) {
91         super(BackgroundThread.getHandler());
92         mContext = context;
93         mKernelCpuThreadReader =
94                 KernelCpuThreadReader.create(
95                         NUM_BUCKETS_DEFAULT, UidPredicate.fromString(COLLECTED_UIDS_DEFAULT));
96         mKernelCpuThreadReaderDiff =
97                 new KernelCpuThreadReaderDiff(
98                         mKernelCpuThreadReader, MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT);
99     }
100 
101     @Override
onChange(boolean selfChange, Uri uri, int userId)102     public void onChange(boolean selfChange, Uri uri, int userId) {
103         updateReader();
104     }
105 
106     /** Update the reader with new settings */
updateReader()107     private void updateReader() {
108         if (mKernelCpuThreadReader == null) {
109             return;
110         }
111 
112         final KeyValueListParser parser = new KeyValueListParser(',');
113         try {
114             parser.setString(
115                     Settings.Global.getString(
116                             mContext.getContentResolver(),
117                             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 =
126                     UidPredicate.fromString(
127                             parser.getString(COLLECTED_UIDS_SETTINGS_KEY, COLLECTED_UIDS_DEFAULT));
128         } 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);
136         mKernelCpuThreadReaderDiff.setMinimumTotalCpuUsageMillis(
137                 parser.getInt(
138                         MINIMUM_TOTAL_CPU_USAGE_MILLIS_SETTINGS_KEY,
139                         MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT));
140     }
141 
142     /** Check whether a UID belongs to a set of UIDs */
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 a
154          * 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)
fromString(String predicateString)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(
173                         Range.create(
174                                 Integer.parseInt(uidRangeMatcher.group(1)),
175                                 Integer.parseInt(uidRangeMatcher.group(2))));
176             }
177             return new UidPredicate(acceptedUidRanges);
178         }
179 
UidPredicate(List<Range<Integer>> acceptedUidRanges)180         private UidPredicate(List<Range<Integer>> acceptedUidRanges) {
181             mAcceptedUidRanges = acceptedUidRanges;
182         }
183 
184         @Override
185         @SuppressWarnings("ForLoopReplaceableByForEach")
test(Integer uid)186         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 }
196