Loading core/java/com/android/internal/os/KernelCpuThreadReader.java +128 −98 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; /** Loading Loading @@ -233,7 +235,7 @@ public class KernelCpuThreadReader { mFrequencyBucketCreator = new FrequencyBucketCreator(mProcTimeInStateReader.getFrequenciesKhz(), numBuckets); mFrequenciesKhz = mFrequencyBucketCreator.getBucketMinFrequencies( mFrequencyBucketCreator.bucketFrequencies( mProcTimeInStateReader.getFrequenciesKhz()); } Loading Loading @@ -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); } Loading Loading @@ -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; } } } Loading core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java +34 −36 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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})); } Loading @@ -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 Loading Loading
core/java/com/android/internal/os/KernelCpuThreadReader.java +128 −98 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; /** Loading Loading @@ -233,7 +235,7 @@ public class KernelCpuThreadReader { mFrequencyBucketCreator = new FrequencyBucketCreator(mProcTimeInStateReader.getFrequenciesKhz(), numBuckets); mFrequenciesKhz = mFrequencyBucketCreator.getBucketMinFrequencies( mFrequencyBucketCreator.bucketFrequencies( mProcTimeInStateReader.getFrequenciesKhz()); } Loading Loading @@ -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); } Loading Loading @@ -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; } } } Loading
core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java +34 −36 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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})); } Loading @@ -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 Loading