Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4b32c9fb authored by Misha Wagner's avatar Misha Wagner
Browse files

Add settings for KernelCpuThreadReader

Settings include:
- Number of buckets
- UID predicate for which threads to collect data for

Test: to test UID selection: `atest KernelCpuThreadReaderTest`
Test: to test settings application: `adb shell cmd settings put ...` and
manual inspection of `pull-source`
Bug: 123562450
Change-Id: I9d97dfc1c120fcf1b04dadb6ce24863afeff053c
parent 4086088a
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -39,7 +39,8 @@ public class KernelCpuThreadReaderPerfTest {
    @Rule
    public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();

    private final KernelCpuThreadReader mKernelCpuThreadReader = KernelCpuThreadReader.create();
    private final KernelCpuThreadReader mKernelCpuThreadReader =
            KernelCpuThreadReader.create(8, uid -> 1000 <= uid && uid < 2000);

    @Test
    public void timeReadCurrentProcessCpuUsage() {
+13 −0
Original line number Diff line number Diff line
@@ -14211,6 +14211,19 @@ public final class Settings {
         */
        public static final String LOOPER_STATS = "looper_stats";
        /**
         * Settings for collecting statistics on CPU usage per thread
         *
         * The following strings are supported as keys:
         * <pre>
         *     num_buckets          (int)
         *     collected_uids       (string)
         * </pre>
         *
         * @hide
         */
        public static final String KERNEL_CPU_THREAD_READER = "kernel_cpu_thread_reader";
        /**
         * Default user id to boot into. They map to user ids, for example, 10, 11, 12.
         *
+52 −44
Original line number Diff line number Diff line
@@ -37,9 +37,10 @@ import java.util.function.Predicate;
 * frequency band.
 *
 * <p>Frequencies are bucketed together to reduce the amount of data created. This means that we
 * return {@link #NUM_BUCKETS} frequencies instead of the full number. Frequencies are reported as
 * the lowest frequency in that range. Frequencies are spread as evenly as possible across the
 * buckets. The buckets do not cross over the little/big frequencies reported.
 * return less frequencies than provided by {@link ProcTimeInStateReader}. The number of
 * frequencies is configurable by {@link #setNumBuckets}. Frequencies are reported as the lowest
 * frequency in that range. Frequencies are spread as evenly as possible across the buckets. The
 * buckets do not cross over the little/big frequencies reported.
 *
 * <p>N.B.: In order to bucket across little/big frequencies correctly, we assume that the {@code
 * time_in_state} file contains every little core frequency in ascending order, followed by every
@@ -97,20 +98,15 @@ public class KernelCpuThreadReader {
            DEFAULT_PROC_PATH.resolve("self/time_in_state");

    /**
     * Number of frequency buckets
     * Value returned when there was an error getting an integer ID value (e.g. PID, UID)
     */
    private static final int NUM_BUCKETS = 8;
    private static final int ID_ERROR = -1;

    /**
     * Default predicate for what UIDs to check for when getting processes. This filters to only
     * select UID 1000 (the {@code system} user)
     * When checking whether to report data for a thread, we check the UID of the thread's owner
     * against this predicate
     */
    private static final Predicate<Integer> DEFAULT_UID_PREDICATE = uid -> uid == 1000;

    /**
     * Value returned when there was an error getting an integer ID value (e.g. PID, UID)
     */
    private static final int ID_ERROR = -1;
    private Predicate<Integer> mUidPredicate;

    /**
     * Where the proc filesystem is mounted
@@ -121,7 +117,7 @@ public class KernelCpuThreadReader {
     * Frequencies read from the {@code time_in_state} file. Read from {@link
     * #mProcTimeInStateReader#getCpuFrequenciesKhz()} and cast to {@code int[]}
     */
    private final int[] mFrequenciesKhz;
    private int[] mFrequenciesKhz;

    /**
     * Used to read and parse {@code time_in_state} files
@@ -131,17 +127,10 @@ public class KernelCpuThreadReader {
    /**
     * Used to sort frequencies and usage times into buckets
     */
    private final FrequencyBucketCreator mFrequencyBucketCreator;
    private FrequencyBucketCreator mFrequencyBucketCreator;

    private final Injector mInjector;

    private KernelCpuThreadReader() throws IOException {
        this(
                DEFAULT_PROC_PATH,
                DEFAULT_INITIAL_TIME_IN_STATE_PATH,
                new Injector());
    }

    /**
     * Create with a path where `proc` is mounted. Used primarily for testing
     *
@@ -151,17 +140,16 @@ public class KernelCpuThreadReader {
     */
    @VisibleForTesting
    public KernelCpuThreadReader(
            int numBuckets,
            Predicate<Integer> uidPredicate,
            Path procPath,
            Path initialTimeInStatePath,
            Injector injector) throws IOException {
        mUidPredicate = uidPredicate;
        mProcPath = procPath;
        mProcTimeInStateReader = new ProcTimeInStateReader(initialTimeInStatePath);
        mInjector = injector;

        // Copy mProcTimeInState's frequencies and initialize bucketing
        final long[] frequenciesKhz = mProcTimeInStateReader.getFrequenciesKhz();
        mFrequencyBucketCreator = new FrequencyBucketCreator(frequenciesKhz, NUM_BUCKETS);
        mFrequenciesKhz = mFrequencyBucketCreator.getBucketMinFrequencies(frequenciesKhz);
        setNumBuckets(numBuckets);
    }

    /**
@@ -170,23 +158,20 @@ public class KernelCpuThreadReader {
     * @return the reader, null if an exception was thrown during creation
     */
    @Nullable
    public static KernelCpuThreadReader create() {
    public static KernelCpuThreadReader create(int numBuckets, Predicate<Integer> uidPredicate) {
        try {
            return new KernelCpuThreadReader();
            return new KernelCpuThreadReader(
                    numBuckets,
                    uidPredicate,
                    DEFAULT_PROC_PATH,
                    DEFAULT_INITIAL_TIME_IN_STATE_PATH,
                    new Injector());
        } catch (IOException e) {
            Slog.e(TAG, "Failed to initialize KernelCpuThreadReader", e);
            return null;
        }
    }

    /**
     * Get the per-thread CPU usage of all processes belonging to UIDs between {@code [1000, 2000)}
     */
    @Nullable
    public ArrayList<ProcessCpuUsage> getProcessCpuUsageByUids() {
        return getProcessCpuUsageByUids(DEFAULT_UID_PREDICATE);
    }

    /**
     * Get the per-thread CPU usage of all processes belonging to a set of UIDs
     *
@@ -195,10 +180,11 @@ public class KernelCpuThreadReader {
     * approximately 500ms on a Pixel 2. Therefore, this method can be computationally expensive,
     * and should not be called more than once an hour.
     *
     * @param uidPredicate only get usage from processes owned by UIDs that match this predicate
     * <p>Data is only collected for UIDs passing the predicate supplied in {@link
     * #setUidPredicate}.
     */
    @Nullable
    public ArrayList<ProcessCpuUsage> getProcessCpuUsageByUids(Predicate<Integer> uidPredicate) {
    public ArrayList<ProcessCpuUsage> getProcessCpuUsageByUids() {
        if (DEBUG) {
            Slog.d(TAG, "Reading CPU thread usages for processes owned by UIDs");
        }
@@ -213,7 +199,7 @@ public class KernelCpuThreadReader {
                if (uid == ID_ERROR || processId == ID_ERROR) {
                    continue;
                }
                if (!uidPredicate.test(uid)) {
                if (!mUidPredicate.test(uid)) {
                    continue;
                }

@@ -247,10 +233,7 @@ public class KernelCpuThreadReader {
     */
    @Nullable
    public ProcessCpuUsage getCurrentProcessCpuUsage() {
        return getProcessCpuUsage(
                mProcPath.resolve("self"),
                mInjector.myPid(),
                mInjector.myUid());
        return getProcessCpuUsage(mProcPath.resolve("self"), mInjector.myPid(), mInjector.myUid());
    }

    /**
@@ -299,6 +282,31 @@ public class KernelCpuThreadReader {
                threadCpuUsages);
    }

    /**
     * Set the number of frequency buckets to use
     */
    void setNumBuckets(int numBuckets) {
        if (numBuckets < 1) {
            Slog.w(TAG, "Number of buckets must be at least 1, but was " + numBuckets);
            return;
        }
        // If `numBuckets` hasn't changed since the last set, do nothing
        if (mFrequenciesKhz != null && mFrequenciesKhz.length == numBuckets) {
            return;
        }
        mFrequencyBucketCreator = new FrequencyBucketCreator(
                mProcTimeInStateReader.getFrequenciesKhz(), numBuckets);
        mFrequenciesKhz = mFrequencyBucketCreator.getBucketMinFrequencies(
                mProcTimeInStateReader.getFrequenciesKhz());
    }

    /**
     * Set the UID predicate for {@link #getProcessCpuUsageByUids}
     */
    void setUidPredicate(Predicate<Integer> uidPredicate) {
        mUidPredicate = uidPredicate;
    }

    /**
     * Get the CPU frequencies that correspond to the times reported in
     * {@link ThreadCpuUsage#usageTimesMillis}
+182 −0
Original line number Diff line number Diff line
/*
 * 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.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 = "1000-1000";

    private final Context mContext;

    @Nullable
    private final KernelCpuThreadReader mKernelCpuThreadReader;

    /**
     * @return returns a created {@link KernelCpuThreadReader} that will be modified by any
     * change in settings, returns null if creation failed
     */
    @Nullable
    public static KernelCpuThreadReader 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.mKernelCpuThreadReader;
    }

    private KernelCpuThreadReaderSettingsObserver(Context context) {
        super(BackgroundThread.getHandler());
        mContext = context;
        mKernelCpuThreadReader = KernelCpuThreadReader.create(
                NUM_BUCKETS_DEFAULT,
                UidPredicate.fromString(COLLECTED_UIDS_DEFAULT));
    }

    @Override
    public void onChange(boolean selfChange, Uri uri, 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);
    }

    /**
     * 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
        public boolean test(Integer uid) {
            for (int i = 0; i < mAcceptedUidRanges.size(); i++) {
                if (mAcceptedUidRanges.get(i).contains(uid)) {
                    return true;
                }
            }
            return false;
        }
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -293,6 +293,7 @@ public class SettingsBackupTest {
                    Settings.Global.INTENT_FIREWALL_UPDATE_METADATA_URL,
                    Settings.Global.JOB_SCHEDULER_CONSTANTS,
                    Settings.Global.KEEP_PROFILE_IN_BACKGROUND,
                    Settings.Global.KERNEL_CPU_THREAD_READER,
                    Settings.Global.LANG_ID_UPDATE_CONTENT_URL,
                    Settings.Global.LANG_ID_UPDATE_METADATA_URL,
                    Settings.Global.LAST_ACTIVE_USER_ID,
Loading