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

Commit 3bca79c9 authored by Misha Wagner's avatar Misha Wagner Committed by Android (Google) Code Review
Browse files

Merge "Add settings for KernelCpuThreadReader"

parents c62a965e 4b32c9fb
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
@@ -14327,6 +14327,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
@@ -297,6 +297,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