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