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