summaryrefslogtreecommitdiff
path: root/core/java/com/android/internal/os/KernelCpuThreadReaderSettingsObserver.java
blob: c908b8cb7264f78fbbf396ccb54e1ff849a42698 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.os;

import android.annotation.Nullable;
import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.KeyValueListParser;
import android.util.Range;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Service that handles settings for {@link KernelCpuThreadReader}
 *
 * <p>N.B.: The `collected_uids` setting takes a string representation of what UIDs to collect data
 * for. A string representation is used as we will want to express UID ranges, therefore an integer
 * array could not be used. The format of the string representation is detailed here: {@link
 * UidPredicate#fromString}.
 *
 * @hide Only for use within the system server
 */
public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {
    private static final String TAG = "KernelCpuThreadReaderSettingsObserver";

    /** The number of frequency buckets to report */
    private static final String NUM_BUCKETS_SETTINGS_KEY = "num_buckets";

    private static final int NUM_BUCKETS_DEFAULT = 8;

    /** List of UIDs to report data for */
    private static final String COLLECTED_UIDS_SETTINGS_KEY = "collected_uids";

    private static final String COLLECTED_UIDS_DEFAULT = "0-0;1000-1000";

    /** Minimum total CPU usage to report */
    private static final String MINIMUM_TOTAL_CPU_USAGE_MILLIS_SETTINGS_KEY =
            "minimum_total_cpu_usage_millis";

    private static final int MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT = 10000;

    private final Context mContext;

    @Nullable private final KernelCpuThreadReader mKernelCpuThreadReader;

    @Nullable private final KernelCpuThreadReaderDiff mKernelCpuThreadReaderDiff;

    /**
     * @return returns a created {@link KernelCpuThreadReader} that will be modified by any change
     *     in settings, returns null if creation failed
     */
    @Nullable
    public static KernelCpuThreadReaderDiff getSettingsModifiedReader(Context context) {
        // Create the observer
        KernelCpuThreadReaderSettingsObserver settingsObserver =
                new KernelCpuThreadReaderSettingsObserver(context);
        // Register the observer to listen for setting changes
        Uri settingsUri = Settings.Global.getUriFor(Settings.Global.KERNEL_CPU_THREAD_READER);
        context.getContentResolver()
                .registerContentObserver(
                        settingsUri, false, settingsObserver, UserHandle.USER_SYSTEM);
        // Return the observer's reader
        return settingsObserver.mKernelCpuThreadReaderDiff;
    }

    private KernelCpuThreadReaderSettingsObserver(Context context) {
        super(BackgroundThread.getHandler());
        mContext = context;
        mKernelCpuThreadReader =
                KernelCpuThreadReader.create(
                        NUM_BUCKETS_DEFAULT, UidPredicate.fromString(COLLECTED_UIDS_DEFAULT));
        mKernelCpuThreadReaderDiff =
                mKernelCpuThreadReader == null
                        ? null
                        : new KernelCpuThreadReaderDiff(
                                mKernelCpuThreadReader, MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT);
    }

    @Override
    public void onChange(boolean selfChange, Collection<Uri> uris, int flags, int userId) {
        updateReader();
    }

    /** Update the reader with new settings */
    private void updateReader() {
        if (mKernelCpuThreadReader == null) {
            return;
        }

        final KeyValueListParser parser = new KeyValueListParser(',');
        try {
            parser.setString(
                    Settings.Global.getString(
                            mContext.getContentResolver(),
                            Settings.Global.KERNEL_CPU_THREAD_READER));
        } catch (IllegalArgumentException e) {
            Slog.e(TAG, "Bad settings", e);
            return;
        }

        final UidPredicate uidPredicate;
        try {
            uidPredicate =
                    UidPredicate.fromString(
                            parser.getString(COLLECTED_UIDS_SETTINGS_KEY, COLLECTED_UIDS_DEFAULT));
        } catch (NumberFormatException e) {
            Slog.w(TAG, "Failed to get UID predicate", e);
            return;
        }

        mKernelCpuThreadReader.setNumBuckets(
                parser.getInt(NUM_BUCKETS_SETTINGS_KEY, NUM_BUCKETS_DEFAULT));
        mKernelCpuThreadReader.setUidPredicate(uidPredicate);
        mKernelCpuThreadReaderDiff.setMinimumTotalCpuUsageMillis(
                parser.getInt(
                        MINIMUM_TOTAL_CPU_USAGE_MILLIS_SETTINGS_KEY,
                        MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT));
    }

    /** Check whether a UID belongs to a set of UIDs */
    @VisibleForTesting
    public static class UidPredicate implements Predicate<Integer> {
        private static final Pattern UID_RANGE_PATTERN = Pattern.compile("([0-9]+)-([0-9]+)");
        private static final String UID_SPECIFIER_DELIMITER = ";";
        private final List<Range<Integer>> mAcceptedUidRanges;

        /**
         * Create a UID predicate from a string representing a list of UID ranges
         *
         * <p>UID ranges are a pair of integers separated by a '-'. If you want to specify a single
         * UID (e.g. UID 1000), you can use {@code 1000-1000}. Lists of ranges are separated by a
         * single ';'. For example, this would be a valid string representation: {@code
         * "1000-1999;2003-2003;2004-2004;2050-2060"}.
         *
         * <p>We do not use ',' to delimit as it is already used in separating different setting
         * arguments.
         *
         * @throws NumberFormatException if the input string is incorrectly formatted
         * @throws IllegalArgumentException if an UID range has a lower end than start
         */
        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
        public static UidPredicate fromString(String predicateString) throws NumberFormatException {
            final List<Range<Integer>> acceptedUidRanges = new ArrayList<>();
            for (String uidSpecifier : predicateString.split(UID_SPECIFIER_DELIMITER)) {
                final Matcher uidRangeMatcher = UID_RANGE_PATTERN.matcher(uidSpecifier);
                if (!uidRangeMatcher.matches()) {
                    throw new NumberFormatException(
                            "Failed to recognize as number range: " + uidSpecifier);
                }
                acceptedUidRanges.add(
                        Range.create(
                                Integer.parseInt(uidRangeMatcher.group(1)),
                                Integer.parseInt(uidRangeMatcher.group(2))));
            }
            return new UidPredicate(acceptedUidRanges);
        }

        private UidPredicate(List<Range<Integer>> acceptedUidRanges) {
            mAcceptedUidRanges = acceptedUidRanges;
        }

        @Override
        @SuppressWarnings("ForLoopReplaceableByForEach")
        public boolean test(Integer uid) {
            for (int i = 0; i < mAcceptedUidRanges.size(); i++) {
                if (mAcceptedUidRanges.get(i).contains(uid)) {
                    return true;
                }
            }
            return false;
        }
    }
}