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

Commit a423d348 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Change KernelCpuThreadReader frequency bucketing to work with >2 core clusters"

parents 44de1187 2d0caa49
Loading
Loading
Loading
Loading
+128 −98
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.os.Process;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;

import java.io.IOException;
@@ -29,6 +30,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.Predicate;

/**
@@ -233,7 +235,7 @@ public class KernelCpuThreadReader {
        mFrequencyBucketCreator =
                new FrequencyBucketCreator(mProcTimeInStateReader.getFrequenciesKhz(), numBuckets);
        mFrequenciesKhz =
                mFrequencyBucketCreator.getBucketMinFrequencies(
                mFrequencyBucketCreator.bucketFrequencies(
                        mProcTimeInStateReader.getFrequenciesKhz());
    }

@@ -317,7 +319,7 @@ public class KernelCpuThreadReader {
        if (cpuUsagesLong == null) {
            return null;
        }
        int[] cpuUsages = mFrequencyBucketCreator.getBucketedValues(cpuUsagesLong);
        int[] cpuUsages = mFrequencyBucketCreator.bucketValues(cpuUsagesLong);

        return new ThreadCpuUsage(threadId, threadName, cpuUsages);
    }
@@ -359,128 +361,156 @@ public class KernelCpuThreadReader {
        }
    }

    /** Puts frequencies and usage times into buckets */
    @VisibleForTesting
    public static class FrequencyBucketCreator {
        private final int mNumBuckets;
        private final int mNumFrequencies;
        private final int mBigFrequenciesStartIndex;
        private final int mLittleNumBuckets;
        private final int mBigNumBuckets;
        private final int mLittleBucketSize;
        private final int mBigBucketSize;

    /**
         * Buckets based of a list of frequencies
     * Quantizes a list of N frequencies into a list of M frequencies (where M<=N)
     *
     * <p>In order to reduce data sent from the device, we discard precise frequency information for
     * an approximation. This is done by putting groups of adjacent frequencies into the same
     * bucket, and then reporting that bucket under the minimum frequency in that bucket.
     *
     * <p>Many devices have multiple core clusters. We do not want to report frequencies from
     * different clusters under the same bucket, so some complication arises.
     *
         * @param frequencies the frequencies to base buckets off
         * @param numBuckets how many buckets to create
     * <p>Buckets are allocated evenly across all core clusters, i.e. they all have the same number
     * of buckets regardless of how many frequencies they contain. This is done to reduce code
     * complexity, and in practice the number of frequencies doesn't vary too much between core
     * clusters.
     *
     * <p>If the number of buckets is not a factor of the number of frequencies, the remainder of
     * the frequencies are placed into the last bucket.
     *
     * <p>It is possible to have less buckets than asked for, so any calling code can't assume that
     * initializing with N buckets will use return N values. This happens in two scenarios:
     *
     * <ul>
     *   <li>There are less frequencies available than buckets asked for.
     *   <li>There are less frequencies in a core cluster than buckets allocated to that core
     *       cluster.
     * </ul>
     */
    @VisibleForTesting
        public FrequencyBucketCreator(long[] frequencies, int numBuckets) {
            Preconditions.checkArgument(numBuckets > 0);
    public static class FrequencyBucketCreator {
        private final int mNumFrequencies;
        private final int mNumBuckets;
        private final int[] mBucketStartIndices;

        @VisibleForTesting
        public FrequencyBucketCreator(long[] frequencies, int targetNumBuckets) {
            mNumFrequencies = frequencies.length;
            mBigFrequenciesStartIndex = getBigFrequenciesStartIndex(frequencies);

            final int littleNumBuckets;
            final int bigNumBuckets;
            if (mBigFrequenciesStartIndex < frequencies.length) {
                littleNumBuckets = numBuckets / 2;
                bigNumBuckets = numBuckets - littleNumBuckets;
            } else {
                // If we've got no big frequencies, set all buckets to little frequencies
                littleNumBuckets = numBuckets;
                bigNumBuckets = 0;
            int[] clusterStartIndices = getClusterStartIndices(frequencies);
            mBucketStartIndices =
                    getBucketStartIndices(clusterStartIndices, targetNumBuckets, mNumFrequencies);
            mNumBuckets = mBucketStartIndices.length;
        }

            // Ensure that we don't have more buckets than frequencies
            mLittleNumBuckets = Math.min(littleNumBuckets, mBigFrequenciesStartIndex);
            mBigNumBuckets =
                    Math.min(bigNumBuckets, frequencies.length - mBigFrequenciesStartIndex);
            mNumBuckets = mLittleNumBuckets + mBigNumBuckets;

            // Set the size of each little and big bucket. If they have no buckets, the size is zero
            mLittleBucketSize =
                    mLittleNumBuckets == 0 ? 0 : mBigFrequenciesStartIndex / mLittleNumBuckets;
            mBigBucketSize =
                    mBigNumBuckets == 0
                            ? 0
                            : (frequencies.length - mBigFrequenciesStartIndex) / mBigNumBuckets;
        }

        /** Find the index where frequencies change from little core to big core */
        /**
         * Put an array of values into buckets. This takes a {@code long[]} and returns {@code
         * int[]} as everywhere this method is used will have to do the conversion anyway, so we
         * save time by doing it here instead
         *
         * @param values the values to bucket
         * @return the bucketed usage times
         */
        @VisibleForTesting
        public static int getBigFrequenciesStartIndex(long[] frequenciesKhz) {
            for (int i = 0; i < frequenciesKhz.length - 1; i++) {
                if (frequenciesKhz[i] > frequenciesKhz[i + 1]) {
                    return i + 1;
        public int[] bucketValues(long[] values) {
            Preconditions.checkArgument(values.length == mNumFrequencies);
            int[] buckets = new int[mNumBuckets];
            for (int bucketIdx = 0; bucketIdx < mNumBuckets; bucketIdx++) {
                final int bucketStartIdx = getLowerBound(bucketIdx, mBucketStartIndices);
                final int bucketEndIdx =
                        getUpperBound(bucketIdx, mBucketStartIndices, values.length);
                for (int valuesIdx = bucketStartIdx; valuesIdx < bucketEndIdx; valuesIdx++) {
                    buckets[bucketIdx] += values[valuesIdx];
                }
            }

            return frequenciesKhz.length;
            return buckets;
        }

        /** Get the minimum frequency in each bucket */
        @VisibleForTesting
        public int[] getBucketMinFrequencies(long[] frequenciesKhz) {
            Preconditions.checkArgument(frequenciesKhz.length == mNumFrequencies);
            // If there's only one bucket, we bucket everything together so the first bucket is the
            // min frequency
            if (mNumBuckets == 1) {
                return new int[] {(int) frequenciesKhz[0]};
            }

            final int[] bucketMinFrequencies = new int[mNumBuckets];
            // Initialize little buckets min frequencies
            for (int i = 0; i < mLittleNumBuckets; i++) {
                bucketMinFrequencies[i] = (int) frequenciesKhz[i * mLittleBucketSize];
        public int[] bucketFrequencies(long[] frequencies) {
            Preconditions.checkArgument(frequencies.length == mNumFrequencies);
            int[] buckets = new int[mNumBuckets];
            for (int i = 0; i < buckets.length; i++) {
                buckets[i] = (int) frequencies[mBucketStartIndices[i]];
            }
            // Initialize big buckets min frequencies
            for (int i = 0; i < mBigNumBuckets; i++) {
                final int frequencyIndex = mBigFrequenciesStartIndex + i * mBigBucketSize;
                bucketMinFrequencies[mLittleNumBuckets + i] = (int) frequenciesKhz[frequencyIndex];
            }
            return bucketMinFrequencies;
            return buckets;
        }

        /**
         * Put an array of values into buckets. This takes a {@code long[]} and returns {@code
         * int[]} as everywhere this method is used will have to do the conversion anyway, so we
         * save time by doing it here instead
         * Get the index in frequencies where each core cluster starts
         *
         * @param values the values to bucket
         * @return the bucketed usage times
         * <p>The frequencies for each cluster are given in ascending order, appended to each other.
         * This means that every time there is a decrease in frequencies (instead of increase) a new
         * cluster has started.
         */
        @VisibleForTesting
        @SuppressWarnings("ForLoopReplaceableByForEach")
        public int[] getBucketedValues(long[] values) {
            Preconditions.checkArgument(values.length == mNumFrequencies);
            final int[] bucketed = new int[mNumBuckets];
        private static int[] getClusterStartIndices(long[] frequencies) {
            ArrayList<Integer> indices = new ArrayList<>();
            indices.add(0);
            for (int i = 0; i < frequencies.length - 1; i++) {
                if (frequencies[i] >= frequencies[i + 1]) {
                    indices.add(i + 1);
                }
            }
            return ArrayUtils.convertToIntArray(indices);
        }

            // If there's only one bucket, add all frequencies in
            if (mNumBuckets == 1) {
                for (int i = 0; i < values.length; i++) {
                    bucketed[0] += values[i];
        /** Get the index in frequencies where each bucket starts */
        private static int[] getBucketStartIndices(
                int[] clusterStartIndices, int targetNumBuckets, int numFrequencies) {
            int numClusters = clusterStartIndices.length;

            // If we haven't got enough buckets for every cluster, we instead have one bucket per
            // cluster, with the last bucket containing the remaining clusters
            if (numClusters > targetNumBuckets) {
                return Arrays.copyOfRange(clusterStartIndices, 0, targetNumBuckets);
            }
                return bucketed;

            ArrayList<Integer> bucketStartIndices = new ArrayList<>();
            for (int clusterIdx = 0; clusterIdx < numClusters; clusterIdx++) {
                final int clusterStartIdx = getLowerBound(clusterIdx, clusterStartIndices);
                final int clusterEndIdx =
                        getUpperBound(clusterIdx, clusterStartIndices, numFrequencies);

                final int numBucketsInCluster;
                if (clusterIdx != numClusters - 1) {
                    numBucketsInCluster = targetNumBuckets / numClusters;
                } else {
                    // If we're in the last cluster, the bucket will contain the remainder of the
                    // frequencies
                    int previousBucketsInCluster = targetNumBuckets / numClusters;
                    numBucketsInCluster =
                            targetNumBuckets - (previousBucketsInCluster * (numClusters - 1));
                }

            // Initialize the little buckets
            for (int i = 0; i < mBigFrequenciesStartIndex; i++) {
                final int bucketIndex = Math.min(i / mLittleBucketSize, mLittleNumBuckets - 1);
                bucketed[bucketIndex] += values[i];
                final int numFrequenciesInCluster = clusterEndIdx - clusterStartIdx;
                // If there are less frequencies than buckets in a cluster, we have one bucket per
                // frequency, and do not use the remaining buckets
                final int numFrequenciesInBucket =
                        Math.max(1, numFrequenciesInCluster / numBucketsInCluster);
                for (int bucketIdx = 0; bucketIdx < numBucketsInCluster; bucketIdx++) {
                    int bucketStartIdx = clusterStartIdx + bucketIdx * numFrequenciesInBucket;
                    // If we've gone over the end index, ignore the rest of the buckets for this
                    // cluster
                    if (bucketStartIdx >= clusterEndIdx) {
                        break;
                    }
                    bucketStartIndices.add(bucketStartIdx);
                }
            // Initialize the big buckets
            for (int i = mBigFrequenciesStartIndex; i < values.length; i++) {
                final int bucketIndex =
                        Math.min(
                                mLittleNumBuckets
                                        + (i - mBigFrequenciesStartIndex) / mBigBucketSize,
                                mNumBuckets - 1);
                bucketed[bucketIndex] += values[i];
            }
            return bucketed;
            return ArrayUtils.convertToIntArray(bucketStartIndices);
        }

        private static int getLowerBound(int index, int[] startIndices) {
            return startIndices[index];
        }

        private static int getUpperBound(int index, int[] startIndices, int max) {
            if (index != startIndices.length - 1) {
                return startIndices[index + 1];
            } else {
                return max;
            }
        }
    }

+34 −36
Original line number Diff line number Diff line
@@ -176,10 +176,10 @@ public class KernelCpuThreadReaderTest {
                frequencies, 4);
        assertArrayEquals(
                new int[]{1, 3, 1, 3},
                frequencyBucketCreator.getBucketMinFrequencies(frequencies));
                frequencyBucketCreator.bucketFrequencies(frequencies));
        assertArrayEquals(
                new int[]{2, 2, 2, 2},
                frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
                frequencyBucketCreator.bucketValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
    }

    @Test
@@ -190,10 +190,10 @@ public class KernelCpuThreadReaderTest {
                frequencies, 4);
        assertArrayEquals(
                new int[]{1, 3, 5, 7},
                frequencyBucketCreator.getBucketMinFrequencies(frequencies));
                frequencyBucketCreator.bucketFrequencies(frequencies));
        assertArrayEquals(
                new int[]{2, 2, 2, 2},
                frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
                frequencyBucketCreator.bucketValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
    }

    @Test
@@ -204,10 +204,10 @@ public class KernelCpuThreadReaderTest {
                frequencies, 4);
        assertArrayEquals(
                new int[]{1, 3, 1, 2},
                frequencyBucketCreator.getBucketMinFrequencies(frequencies));
                frequencyBucketCreator.bucketFrequencies(frequencies));
        assertArrayEquals(
                new int[]{2, 3, 1, 2},
                frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
                frequencyBucketCreator.bucketValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
    }

    @Test
@@ -218,10 +218,10 @@ public class KernelCpuThreadReaderTest {
                frequencies, 4);
        assertArrayEquals(
                new int[]{1, 2, 1, 3},
                frequencyBucketCreator.getBucketMinFrequencies(frequencies));
                frequencyBucketCreator.bucketFrequencies(frequencies));
        assertArrayEquals(
                new int[]{1, 2, 2, 3},
                frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
                frequencyBucketCreator.bucketValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
    }

    @Test
@@ -232,10 +232,10 @@ public class KernelCpuThreadReaderTest {
                frequencies, 8);
        assertArrayEquals(
                new int[]{1, 2, 3, 4, 1, 2, 3, 4},
                frequencyBucketCreator.getBucketMinFrequencies(frequencies));
                frequencyBucketCreator.bucketFrequencies(frequencies));
        assertArrayEquals(
                new int[]{1, 1, 1, 1, 1, 1, 1, 1},
                frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
                frequencyBucketCreator.bucketValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
    }

    @Test
@@ -246,10 +246,10 @@ public class KernelCpuThreadReaderTest {
                frequencies, 8);
        assertArrayEquals(
                new int[]{1, 3, 5, 7, 1, 2, 3},
                frequencyBucketCreator.getBucketMinFrequencies(frequencies));
                frequencyBucketCreator.bucketFrequencies(frequencies));
        assertArrayEquals(
                new int[]{2, 2, 2, 3, 1, 1, 1},
                frequencyBucketCreator.getBucketedValues(
                frequencyBucketCreator.bucketValues(
                        new long[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}));
    }

@@ -261,39 +261,37 @@ public class KernelCpuThreadReaderTest {
                frequencies, 1);
        assertArrayEquals(
                new int[]{1},
                frequencyBucketCreator.getBucketMinFrequencies(frequencies));
                frequencyBucketCreator.bucketFrequencies(frequencies));
        assertArrayEquals(
                new int[]{8},
                frequencyBucketCreator.getBucketedValues(
                frequencyBucketCreator.bucketValues(
                        new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
    }

    @Test
    public void testGetBigFrequenciesStartIndex_simple() {
        assertEquals(
                3, KernelCpuThreadReader.FrequencyBucketCreator.getBigFrequenciesStartIndex(
                        new long[]{1, 2, 3, 1, 2, 3}));
    }

    @Test
    public void testGetBigFrequenciesStartIndex_moreLittle() {
        assertEquals(
                4, KernelCpuThreadReader.FrequencyBucketCreator.getBigFrequenciesStartIndex(
                        new long[]{1, 2, 3, 4, 1, 2}));
    }

    @Test
    public void testGetBigFrequenciesStartIndex_moreBig() {
        assertEquals(
                2, KernelCpuThreadReader.FrequencyBucketCreator.getBigFrequenciesStartIndex(
                        new long[]{1, 2, 1, 2, 3, 4}));
    public void testBucketSetup_threeClusters() {
        long[] frequencies = {1, 2, 3, 4, 2, 3, 4, 5, 3, 4, 5, 6};
        KernelCpuThreadReader.FrequencyBucketCreator frequencyBucketCreator =
                new KernelCpuThreadReader.FrequencyBucketCreator(frequencies, 6);
        assertArrayEquals(
                new int[] {1, 3, 2, 4, 3, 5},
                frequencyBucketCreator.bucketFrequencies(frequencies));
        assertArrayEquals(
                new int[] {2, 2, 2, 2, 2, 2},
                frequencyBucketCreator.bucketValues(
                        new long[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}));
    }

    @Test
    public void testGetBigFrequenciesStartIndex_noBig() {
        assertEquals(
                4, KernelCpuThreadReader.FrequencyBucketCreator.getBigFrequenciesStartIndex(
                        new long[]{1, 2, 3, 4}));
    public void testBucketSetup_moreClustersThanBuckets() {
        long[] frequencies = {1, 1, 1, 1, 1, 1, 1, 1};
        KernelCpuThreadReader.FrequencyBucketCreator frequencyBucketCreator =
                new KernelCpuThreadReader.FrequencyBucketCreator(frequencies, 4);
        assertArrayEquals(
                new int[] {1, 1, 1, 1}, frequencyBucketCreator.bucketFrequencies(frequencies));
        assertArrayEquals(
                new int[] {1, 1, 1, 5},
                frequencyBucketCreator.bucketValues(new long[] {1, 1, 1, 1, 1, 1, 1, 1}));
    }

    @Test