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

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

Merge "Add diffing to KernelCpuThreadReader"

parents aa07312d 2487d357
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -40,7 +40,7 @@ public class KernelCpuThreadReaderPerfTest {
    public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();

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

    @Test
    public void timeReadCurrentProcessCpuUsage() {
+7 −69
Original line number Diff line number Diff line
@@ -92,24 +92,12 @@ public class KernelCpuThreadReader {
    /** Value returned when there was an error getting an integer ID value (e.g. PID, UID) */
    private static final int ID_ERROR = -1;

    /** Thread ID used when reporting CPU used by other threads */
    private static final int OTHER_THREADS_ID = -1;

    /** Thread name used when reporting CPU used by other threads */
    private static final String OTHER_THREADS_NAME = "__OTHER_THREADS";

    /**
     * When checking whether to report data for a thread, we check the UID of the thread's owner
     * against this predicate
     */
    private Predicate<Integer> mUidPredicate;

    /**
     * If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it
     * will not be reported
     */
    private int mMinimumTotalCpuUsageMillis;

    /** Where the proc filesystem is mounted */
    private final Path mProcPath;

@@ -138,13 +126,11 @@ public class KernelCpuThreadReader {
    public KernelCpuThreadReader(
            int numBuckets,
            Predicate<Integer> uidPredicate,
            int minimumTotalCpuUsageMillis,
            Path procPath,
            Path initialTimeInStatePath,
            Injector injector)
            throws IOException {
        mUidPredicate = uidPredicate;
        mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis;
        mProcPath = procPath;
        mProcTimeInStateReader = new ProcTimeInStateReader(initialTimeInStatePath);
        mInjector = injector;
@@ -157,13 +143,11 @@ public class KernelCpuThreadReader {
     * @return the reader, null if an exception was thrown during creation
     */
    @Nullable
    public static KernelCpuThreadReader create(
            int numBuckets, Predicate<Integer> uidPredicate, int minimumTotalCpuUsageMillis) {
    public static KernelCpuThreadReader create(int numBuckets, Predicate<Integer> uidPredicate) {
        try {
            return new KernelCpuThreadReader(
                    numBuckets,
                    uidPredicate,
                    minimumTotalCpuUsageMillis,
                    DEFAULT_PROC_PATH,
                    DEFAULT_INITIAL_TIME_IN_STATE_PATH,
                    new Injector());
@@ -258,18 +242,6 @@ public class KernelCpuThreadReader {
        mUidPredicate = uidPredicate;
    }

    /**
     * If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it
     * will not be reported
     */
    void setMinimumTotalCpuUsageMillis(int minimumTotalCpuUsageMillis) {
        if (minimumTotalCpuUsageMillis < 0) {
            Slog.w(TAG, "Negative minimumTotalCpuUsageMillis: " + minimumTotalCpuUsageMillis);
            return;
        }
        mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis;
    }

    /**
     * Read all of the CPU usage statistics for each child thread of a process
     *
@@ -292,7 +264,6 @@ public class KernelCpuThreadReader {
                            + uid);
        }

        int[] filteredThreadsCpuUsage = null;
        final Path allThreadsPath = processPath.resolve("task");
        final ArrayList<ThreadCpuUsage> threadCpuUsages = new ArrayList<>();
        try (DirectoryStream<Path> threadPaths = Files.newDirectoryStream(allThreadsPath)) {
@@ -301,14 +272,6 @@ public class KernelCpuThreadReader {
                if (threadCpuUsage == null) {
                    continue;
                }
                if (mMinimumTotalCpuUsageMillis > totalCpuUsage(threadCpuUsage.usageTimesMillis)) {
                    if (filteredThreadsCpuUsage == null) {
                        filteredThreadsCpuUsage = new int[mFrequenciesKhz.length];
                    }
                    filteredThreadsCpuUsage =
                            sumCpuUsage(filteredThreadsCpuUsage, threadCpuUsage.usageTimesMillis);
                    continue;
                }
                threadCpuUsages.add(threadCpuUsage);
            }
        } catch (IOException e) {
@@ -320,14 +283,6 @@ public class KernelCpuThreadReader {
        if (threadCpuUsages.isEmpty()) {
            return null;
        }

        // Add the filtered out thread CPU usage under an "other threads" ThreadCpuUsage
        if (filteredThreadsCpuUsage != null) {
            threadCpuUsages.add(
                    new ThreadCpuUsage(
                            OTHER_THREADS_ID, OTHER_THREADS_NAME, filteredThreadsCpuUsage));
        }

        if (DEBUG) {
            Slog.d(TAG, "Read CPU usage of " + threadCpuUsages.size() + " threads");
        }
@@ -404,25 +359,6 @@ public class KernelCpuThreadReader {
        }
    }

    /** Get the sum of all CPU usage across all frequencies */
    @SuppressWarnings("ForLoopReplaceableByForEach")
    private static int totalCpuUsage(int[] cpuUsage) {
        int total = 0;
        for (int i = 0; i < cpuUsage.length; i++) {
            total += cpuUsage[i];
        }
        return total;
    }

    /** Add two CPU frequency usages together */
    private static int[] sumCpuUsage(int[] a, int[] b) {
        int[] summed = new int[a.length];
        for (int i = 0; i < a.length; i++) {
            summed[i] = a[i] + b[i];
        }
        return summed;
    }

    /** Puts frequencies and usage times into buckets */
    @VisibleForTesting
    public static class FrequencyBucketCreator {
@@ -553,9 +489,10 @@ public class KernelCpuThreadReader {
        public final int processId;
        public final String processName;
        public final int uid;
        public final ArrayList<ThreadCpuUsage> threadCpuUsages;
        public ArrayList<ThreadCpuUsage> threadCpuUsages;

        ProcessCpuUsage(
        @VisibleForTesting
        public ProcessCpuUsage(
                int processId,
                String processName,
                int uid,
@@ -571,9 +508,10 @@ public class KernelCpuThreadReader {
    public static class ThreadCpuUsage {
        public final int threadId;
        public final String threadName;
        public final int[] usageTimesMillis;
        public int[] usageTimesMillis;

        ThreadCpuUsage(int threadId, String threadName, int[] usageTimesMillis) {
        @VisibleForTesting
        public ThreadCpuUsage(int threadId, String threadName, int[] usageTimesMillis) {
            this.threadId = threadId;
            this.threadName = threadName;
            this.usageTimesMillis = usageTimesMillis;
+305 −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.util.ArrayMap;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * Delegates per-thread CPU collection to {@link KernelCpuThreadReader}, and calculates the
 * difference between CPU usage at each call of {@link #getProcessCpuUsageDiffed()}.
 *
 * <p>Some notes on the diff calculation:
 *
 * <ul>
 *   <li>The diffing is done between each call of {@link #getProcessCpuUsageDiffed()}, i.e. call N
 *       of this method will return CPU used by threads between call N-1 and N.
 *   <li>The first call of {@link #getProcessCpuUsageDiffed()} will return no processes ("first
 *       call" is the first call in the lifetime of a {@link KernelCpuThreadReaderDiff} object).
 *   <li>If a thread does not exist at call N, but does exist at call N+1, the diff will assume that
 *       the CPU usage at call N was zero. Thus, the diff reported will be equivalent to the value
 *       returned by {@link KernelCpuThreadReader#getProcessCpuUsage()} at call N+1.
 *   <li>If an error occurs in {@link KernelCpuThreadReader} at call N, we will return no
 *       information for CPU usage between call N-1 and N (as we don't know the start value) and
 *       between N and N+1 (as we don't know the end value). Assuming all other calls are
 *       successful, the next call to return data will be N+2, for the period between N+1 and N+2.
 *   <li>If an error occurs in this class (but not in {@link KernelCpuThreadReader}) at call N, the
 *       data will only be dropped for call N, as we can still use the CPU data for the surrounding
 *       calls.
 * </ul>
 *
 * <p>Additionally to diffing, this class also contains logic for thresholding reported threads. A
 * thread will not be reported unless its total CPU usage is at least equal to the value set in
 * {@link #setMinimumTotalCpuUsageMillis}. Filtered thread CPU usage is summed and reported under
 * one "other threads" thread. This reduces the cardinality of the {@link
 * #getProcessCpuUsageDiffed()} result.
 *
 * <p>Thresholding is done in this class, instead of {@link KernelCpuThreadReader}, and instead of
 * WestWorld, because the thresholding should be done after diffing, not before. This is because of
 * two issues with thresholding before diffing:
 *
 * <ul>
 *   <li>We would threshold less and less threads as thread uptime increases.
 *   <li>We would encounter errors as the filtered threads become unfiltered, as the "other threads"
 *       result could have negative diffs, and the newly unfiltered threads would have incorrect
 *       diffs that include CPU usage from when they were filtered.
 * </ul>
 *
 * @hide Only for use within the system server
 */
@SuppressWarnings("ForLoopReplaceableByForEach")
public class KernelCpuThreadReaderDiff {
    private static final String TAG = "KernelCpuThreadReaderDiff";

    /** Thread ID used when reporting CPU used by other threads */
    private static final int OTHER_THREADS_ID = -1;

    /** Thread name used when reporting CPU used by other threads */
    private static final String OTHER_THREADS_NAME = "__OTHER_THREADS";

    private final KernelCpuThreadReader mReader;

    /**
     * CPU usage from the previous call of {@link #getProcessCpuUsageDiffed()}. Null if there was no
     * previous call, or if the previous call failed
     *
     * <p>Maps the thread's identifier to the per-frequency CPU usage for that thread. The
     * identifier contains the minimal amount of information to identify a thread (see {@link
     * ThreadKey} for more information), thus reducing memory consumption.
     */
    @Nullable private Map<ThreadKey, int[]> mPreviousCpuUsage;

    /**
     * If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it
     * will not be reported
     */
    private int mMinimumTotalCpuUsageMillis;

    @VisibleForTesting
    public KernelCpuThreadReaderDiff(KernelCpuThreadReader reader, int minimumTotalCpuUsageMillis) {
        mReader = reader;
        mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis;
        mPreviousCpuUsage = null;
    }

    /**
     * Returns the difference in CPU usage since the last time this method was called.
     *
     * @see KernelCpuThreadReader#getProcessCpuUsage()
     */
    @Nullable
    public ArrayList<KernelCpuThreadReader.ProcessCpuUsage> getProcessCpuUsageDiffed() {
        Map<ThreadKey, int[]> newCpuUsage = null;
        try {
            // Get the thread CPU usage and index them by ThreadKey
            final ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages =
                    mReader.getProcessCpuUsage();
            newCpuUsage = createCpuUsageMap(processCpuUsages);
            // If there is no previous CPU usage, return nothing
            if (mPreviousCpuUsage == null) {
                return null;
            }

            // Do diffing and thresholding for each process
            for (int i = 0; i < processCpuUsages.size(); i++) {
                KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = processCpuUsages.get(i);
                changeToDiffs(mPreviousCpuUsage, processCpuUsage);
                applyThresholding(processCpuUsage);
            }
            return processCpuUsages;
        } finally {
            // Always update the previous CPU usage. If we haven't got an update, it will be set to
            // null, so the next call knows there no previous values
            mPreviousCpuUsage = newCpuUsage;
        }
    }

    /** @see KernelCpuThreadReader#getCpuFrequenciesKhz() */
    @Nullable
    public int[] getCpuFrequenciesKhz() {
        return mReader.getCpuFrequenciesKhz();
    }

    /**
     * If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it
     * will not be reported
     */
    void setMinimumTotalCpuUsageMillis(int minimumTotalCpuUsageMillis) {
        if (minimumTotalCpuUsageMillis < 0) {
            Slog.w(TAG, "Negative minimumTotalCpuUsageMillis: " + minimumTotalCpuUsageMillis);
            return;
        }
        mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis;
    }

    /**
     * Create a map of a thread's identifier to a thread's CPU usage. Used for fast indexing when
     * calculating diffs
     */
    private static Map<ThreadKey, int[]> createCpuUsageMap(
            List<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages) {
        final Map<ThreadKey, int[]> cpuUsageMap = new ArrayMap<>();
        for (int i = 0; i < processCpuUsages.size(); i++) {
            KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = processCpuUsages.get(i);
            for (int j = 0; j < processCpuUsage.threadCpuUsages.size(); j++) {
                KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage =
                        processCpuUsage.threadCpuUsages.get(j);
                cpuUsageMap.put(
                        new ThreadKey(
                                processCpuUsage.processId,
                                threadCpuUsage.threadId,
                                processCpuUsage.processName,
                                threadCpuUsage.threadName),
                        threadCpuUsage.usageTimesMillis);
            }
        }
        return cpuUsageMap;
    }

    /**
     * Calculate the difference in per-frequency CPU usage for all threads in a process
     *
     * @param previousCpuUsage CPU usage from the last call, the base of the diff
     * @param processCpuUsage CPU usage from the current call, this value is modified to contain the
     *     diffed values
     */
    private static void changeToDiffs(
            Map<ThreadKey, int[]> previousCpuUsage,
            KernelCpuThreadReader.ProcessCpuUsage processCpuUsage) {
        for (int i = 0; i < processCpuUsage.threadCpuUsages.size(); i++) {
            KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage =
                    processCpuUsage.threadCpuUsages.get(i);
            final ThreadKey key =
                    new ThreadKey(
                            processCpuUsage.processId,
                            threadCpuUsage.threadId,
                            processCpuUsage.processName,
                            threadCpuUsage.threadName);
            int[] previous = previousCpuUsage.get(key);
            if (previous == null) {
                // If there's no previous CPU usage, assume that it's zero
                previous = new int[threadCpuUsage.usageTimesMillis.length];
            }
            threadCpuUsage.usageTimesMillis =
                    cpuTimeDiff(threadCpuUsage.usageTimesMillis, previous);
        }
    }

    /**
     * Filter out any threads with less than {@link #mMinimumTotalCpuUsageMillis} total CPU usage
     *
     * <p>The sum of the CPU usage of filtered threads is added under a single thread, labeled with
     * {@link #OTHER_THREADS_ID} and {@link #OTHER_THREADS_NAME}.
     *
     * @param processCpuUsage CPU usage to apply thresholding to, this value is modified to change
     *     the threads it contains
     */
    private void applyThresholding(KernelCpuThreadReader.ProcessCpuUsage processCpuUsage) {
        int[] filteredThreadsCpuUsage = null;
        final ArrayList<KernelCpuThreadReader.ThreadCpuUsage> thresholded = new ArrayList<>();
        for (int i = 0; i < processCpuUsage.threadCpuUsages.size(); i++) {
            KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage =
                    processCpuUsage.threadCpuUsages.get(i);
            if (mMinimumTotalCpuUsageMillis > totalCpuUsage(threadCpuUsage.usageTimesMillis)) {
                if (filteredThreadsCpuUsage == null) {
                    filteredThreadsCpuUsage = new int[threadCpuUsage.usageTimesMillis.length];
                }
                addToCpuUsage(filteredThreadsCpuUsage, threadCpuUsage.usageTimesMillis);
                continue;
            }
            thresholded.add(threadCpuUsage);
        }
        if (filteredThreadsCpuUsage != null) {
            thresholded.add(
                    new KernelCpuThreadReader.ThreadCpuUsage(
                            OTHER_THREADS_ID, OTHER_THREADS_NAME, filteredThreadsCpuUsage));
        }
        processCpuUsage.threadCpuUsages = thresholded;
    }

    /** Get the sum of all CPU usage across all frequencies */
    private static int totalCpuUsage(int[] cpuUsage) {
        int total = 0;
        for (int i = 0; i < cpuUsage.length; i++) {
            total += cpuUsage[i];
        }
        return total;
    }

    /** Add two CPU frequency usages together */
    private static void addToCpuUsage(int[] a, int[] b) {
        for (int i = 0; i < a.length; i++) {
            a[i] += b[i];
        }
    }

    /** Subtract two CPU frequency usages from each other */
    private static int[] cpuTimeDiff(int[] a, int[] b) {
        int[] difference = new int[a.length];
        for (int i = 0; i < a.length; i++) {
            difference[i] = a[i] - b[i];
        }
        return difference;
    }

    /**
     * Identifies a thread
     *
     * <p>Only stores the minimum amount of information to identify a thread. This includes the
     * PID/TID, but as both are recycled as processes/threads end and begin, we also store the hash
     * of the name of the process/thread.
     */
    private static class ThreadKey {
        private final int mProcessId;
        private final int mThreadId;
        private final int mProcessNameHash;
        private final int mThreadNameHash;

        ThreadKey(int processId, int threadId, String processName, String threadName) {
            this.mProcessId = processId;
            this.mThreadId = threadId;
            // Only store the hash to reduce memory consumption
            this.mProcessNameHash = Objects.hash(processName);
            this.mThreadNameHash = Objects.hash(threadName);
        }

        @Override
        public int hashCode() {
            return Objects.hash(mProcessId, mThreadId, mProcessNameHash, mThreadNameHash);
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof ThreadKey)) {
                return false;
            }
            ThreadKey other = (ThreadKey) obj;
            return mProcessId == other.mProcessId
                    && mThreadId == other.mThreadId
                    && mProcessNameHash == other.mProcessNameHash
                    && mThreadNameHash == other.mThreadNameHash;
        }
    }
}
+9 −6
Original line number Diff line number Diff line
@@ -67,12 +67,14 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {

    @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 KernelCpuThreadReader getSettingsModifiedReader(Context context) {
    public static KernelCpuThreadReaderDiff getSettingsModifiedReader(Context context) {
        // Create the observer
        KernelCpuThreadReaderSettingsObserver settingsObserver =
                new KernelCpuThreadReaderSettingsObserver(context);
@@ -82,7 +84,7 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {
                .registerContentObserver(
                        settingsUri, false, settingsObserver, UserHandle.USER_SYSTEM);
        // Return the observer's reader
        return settingsObserver.mKernelCpuThreadReader;
        return settingsObserver.mKernelCpuThreadReaderDiff;
    }

    private KernelCpuThreadReaderSettingsObserver(Context context) {
@@ -90,9 +92,10 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {
        mContext = context;
        mKernelCpuThreadReader =
                KernelCpuThreadReader.create(
                        NUM_BUCKETS_DEFAULT,
                        UidPredicate.fromString(COLLECTED_UIDS_DEFAULT),
                        MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT);
                        NUM_BUCKETS_DEFAULT, UidPredicate.fromString(COLLECTED_UIDS_DEFAULT));
        mKernelCpuThreadReaderDiff =
                new KernelCpuThreadReaderDiff(
                        mKernelCpuThreadReader, MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT);
    }

    @Override
@@ -130,7 +133,7 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {
        mKernelCpuThreadReader.setNumBuckets(
                parser.getInt(NUM_BUCKETS_SETTINGS_KEY, NUM_BUCKETS_DEFAULT));
        mKernelCpuThreadReader.setUidPredicate(uidPredicate);
        mKernelCpuThreadReader.setMinimumTotalCpuUsageMillis(
        mKernelCpuThreadReaderDiff.setMinimumTotalCpuUsageMillis(
                parser.getInt(
                        MINIMUM_TOTAL_CPU_USAGE_MILLIS_SETTINGS_KEY,
                        MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT));
+242 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading