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

Commit 5dc9d972 authored by Dmitri Plotnikov's avatar Dmitri Plotnikov Committed by Android (Google) Code Review
Browse files

Merge "Make KernelSingleProcessCpuThreadReader aggregate CPU times"

parents 2af78f83 492ed242
Loading
Loading
Loading
Loading
+2 −29
Original line number Diff line number Diff line
@@ -1019,8 +1019,6 @@ public class BatteryStatsImpl extends BatteryStats {
     */
    private LongSamplingCounterArray mSystemServerCpuTimesUs;
    private long[] mTmpSystemServerCpuTimesUs;
    /**
     * Times spent by the system server threads grouped by cluster and CPU speed.
     */
@@ -12432,41 +12430,15 @@ public class BatteryStatsImpl extends BatteryStats {
            return;
        }
        int numCpuClusters = mPowerProfile.getNumCpuClusters();
        if (mSystemServerCpuTimesUs == null) {
            mSystemServerCpuTimesUs = new LongSamplingCounterArray(mOnBatteryTimeBase);
            mSystemServerThreadCpuTimesUs = new LongSamplingCounterArray(mOnBatteryTimeBase);
            mBinderThreadCpuTimesUs = new LongSamplingCounterArray(mOnBatteryTimeBase);
        }
        mSystemServerCpuTimesUs.addCountLocked(systemServiceCpuThreadTimes.processCpuTimesUs);
        mSystemServerThreadCpuTimesUs.addCountLocked(systemServiceCpuThreadTimes.threadCpuTimesUs);
        mBinderThreadCpuTimesUs.addCountLocked(systemServiceCpuThreadTimes.binderThreadCpuTimesUs);
        long totalCpuTimeAllThreads = 0;
        for (int index = systemServiceCpuThreadTimes.threadCpuTimesUs.length - 1; index >= 0;
                index--) {
            totalCpuTimeAllThreads += systemServiceCpuThreadTimes.threadCpuTimesUs[index];
        }
        // Estimate per cluster per frequency CPU time for the entire process
        // by distributing the total process CPU time proportionately to how much
        // CPU time its threads took on those clusters/frequencies.  This algorithm
        // works more accurately when when we have equally distributed concurrency.
        // TODO(b/169279846): obtain actual process CPU times from the kernel
        long processCpuTime = systemServiceCpuThreadTimes.processCpuTimeUs;
        if (mTmpSystemServerCpuTimesUs == null) {
            mTmpSystemServerCpuTimesUs =
                    new long[systemServiceCpuThreadTimes.threadCpuTimesUs.length];
        }
        for (int index = systemServiceCpuThreadTimes.threadCpuTimesUs.length - 1; index >= 0;
                index--) {
            mTmpSystemServerCpuTimesUs[index] =
                    processCpuTime * systemServiceCpuThreadTimes.threadCpuTimesUs[index]
                            / totalCpuTimeAllThreads;
        }
        mSystemServerCpuTimesUs.addCountLocked(mTmpSystemServerCpuTimesUs);
        if (DEBUG_BINDER_STATS) {
            Slog.d(TAG, "System server threads per CPU cluster (binder threads/total threads/%)");
            long totalCpuTimeMs = 0;
@@ -12480,6 +12452,7 @@ public class BatteryStatsImpl extends BatteryStats {
            final long[] binderThreadCpuTimesUs =
                    mBinderThreadCpuTimesUs.getCountsLocked(0);
            int index = 0;
            int numCpuClusters = mPowerProfile.getNumCpuClusters();
            for (int cluster = 0; cluster < numCpuClusters; cluster++) {
                StringBuilder sb = new StringBuilder();
                sb.append("cpu").append(cpuIndex).append(": [");
+57 −39
Original line number Diff line number Diff line
@@ -33,8 +33,7 @@ import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;

/**
 * Iterates over all threads owned by a given process, and return the CPU usage for
@@ -151,7 +150,7 @@ public class KernelSingleProcessCpuThreadReader {

    /**
     * Get the CPU frequencies that correspond to the times reported in {@link
     * ThreadCpuUsage#usageTimesMillis}
     * ProcessCpuUsage#processCpuTimesMillis} etc.
     */
    public int getCpuFrequencyCount() {
        if (mFrequencyCount == 0) {
@@ -163,14 +162,22 @@ public class KernelSingleProcessCpuThreadReader {
    /**
     * Get the total and per-thread CPU usage of the process with the PID specified in the
     * constructor.
     * @param selectedThreadIds a SORTED array of native Thread IDs whose CPU times should
     *                          be aggregated as a group.  This is expected to be a subset
     *                          of all thread IDs owned by the process.
     */
    @Nullable
    public ProcessCpuUsage getProcessCpuUsage() {
    public ProcessCpuUsage getProcessCpuUsage(int[] selectedThreadIds) {
        if (DEBUG) {
            Slog.d(TAG, "Reading CPU thread usages with directory " + mProcPath + " process ID "
                    + mPid);
        }

        if (!isSorted(selectedThreadIds)) {
            throw new IllegalArgumentException("selectedThreadIds is not sorted: "
                    + Arrays.toString(selectedThreadIds));
        }

        if (!Process.readProcFile(mProcessStatFilePath, PROCESS_FULL_STATS_FORMAT, null,
                mProcessFullStatsData, null)) {
            Slog.e(TAG, "Failed to read process stat file " + mProcessStatFilePath);
@@ -182,39 +189,43 @@ public class KernelSingleProcessCpuThreadReader {

        long processCpuTimeMillis = (utime + stime) * mJiffyMillis;

        final ArrayList<ThreadCpuUsage> threadCpuUsages = new ArrayList<>();
        int cpuFrequencyCount = getCpuFrequencyCount();
        ProcessCpuUsage processCpuUsage = new ProcessCpuUsage(cpuFrequencyCount);
        try (DirectoryStream<Path> threadPaths = Files.newDirectoryStream(mThreadsDirectoryPath)) {
            for (Path threadDirectory : threadPaths) {
                ThreadCpuUsage threadCpuUsage = getThreadCpuUsage(threadDirectory);
                if (threadCpuUsage == null) {
                    continue;
                }
                threadCpuUsages.add(threadCpuUsage);
                readThreadCpuUsage(processCpuUsage, selectedThreadIds, threadDirectory);
            }
        } catch (IOException | DirectoryIteratorException e) {
            // Expected when a process finishes
            return null;
        }

        // If we found no threads, then the process has exited while we were reading from it
        if (threadCpuUsages.isEmpty()) {
            return null;
        // Estimate per cluster per frequency CPU time for the entire process
        // by distributing the total process CPU time proportionately to how much
        // CPU time its threads took on those clusters/frequencies.  This algorithm
        // works more accurately when when we have equally distributed concurrency.
        // TODO(b/169279846): obtain actual process CPU times from the kernel
        long totalCpuTimeAllThreads = 0;
        for (int i = cpuFrequencyCount - 1; i >= 0; i--) {
            totalCpuTimeAllThreads += processCpuUsage.threadCpuTimesMillis[i];
        }
        if (DEBUG) {
            Slog.d(TAG, "Read CPU usage of " + threadCpuUsages.size() + " threads");

        for (int i = cpuFrequencyCount - 1; i >= 0; i--) {
            processCpuUsage.processCpuTimesMillis[i] =
                    processCpuTimeMillis * processCpuUsage.threadCpuTimesMillis[i]
                            / totalCpuTimeAllThreads;
        }
        return new ProcessCpuUsage(processCpuTimeMillis, threadCpuUsages);

        return processCpuUsage;
    }

    /**
     * Get a thread's CPU usage
     * Reads a thread's CPU usage and aggregates the per-cluster per-frequency CPU times.
     *
     * @param threadDirectory the {@code /proc} directory of the thread
     * @return thread CPU usage. Null if the thread exited and its {@code proc} directory was
     * removed while collecting information
     */
    @Nullable
    private ThreadCpuUsage getThreadCpuUsage(Path threadDirectory) {
    private void readThreadCpuUsage(ProcessCpuUsage processCpuUsage, int[] selectedThreadIds,
            Path threadDirectory) {
        // Get the thread ID from the directory name
        final int threadId;
        try {
@@ -222,38 +233,45 @@ public class KernelSingleProcessCpuThreadReader {
            threadId = Integer.parseInt(directoryName);
        } catch (NumberFormatException e) {
            Slog.w(TAG, "Failed to parse thread ID when iterating over /proc/*/task", e);
            return null;
            return;
        }

        // Get the CPU statistics from the directory
        final Path threadCpuStatPath = threadDirectory.resolve(CPU_STATISTICS_FILENAME);
        final long[] cpuUsages = mProcTimeInStateReader.getUsageTimesMillis(threadCpuStatPath);
        if (cpuUsages == null) {
            return null;
            return;
        }

        return new ThreadCpuUsage(threadId, cpuUsages);
        final int cpuFrequencyCount = getCpuFrequencyCount();
        final boolean isSelectedThread = Arrays.binarySearch(selectedThreadIds, threadId) >= 0;
        for (int i = cpuFrequencyCount - 1; i >= 0; i--) {
            processCpuUsage.threadCpuTimesMillis[i] += cpuUsages[i];
            if (isSelectedThread) {
                processCpuUsage.selectedThreadCpuTimesMillis[i] += cpuUsages[i];
            }
        }
    }

    /** CPU usage of a process and all of its threads */
    /** CPU usage of a process, all of its threads and a selected subset of its threads */
    public static class ProcessCpuUsage {
        public final long cpuTimeMillis;
        public final List<ThreadCpuUsage> threadCpuUsages;

        ProcessCpuUsage(long cpuTimeMillis, List<ThreadCpuUsage> threadCpuUsages) {
            this.cpuTimeMillis = cpuTimeMillis;
            this.threadCpuUsages = threadCpuUsages;
        public long[] processCpuTimesMillis;
        public long[] threadCpuTimesMillis;
        public long[] selectedThreadCpuTimesMillis;

        public ProcessCpuUsage(int cpuFrequencyCount) {
            processCpuTimesMillis = new long[cpuFrequencyCount];
            threadCpuTimesMillis = new long[cpuFrequencyCount];
            selectedThreadCpuTimesMillis = new long[cpuFrequencyCount];
        }
    }

    /** CPU usage of a thread */
    public static class ThreadCpuUsage {
        public final int threadId;
        public final long[] usageTimesMillis;

        ThreadCpuUsage(int threadId, long[] usageTimesMillis) {
            this.threadId = threadId;
            this.usageTimesMillis = usageTimesMillis;
    private static boolean isSorted(int[] array) {
        for (int i = 0; i < array.length - 1; i++) {
            if (array[i] > array[i + 1]) {
                return false;
            }
        }
        return true;
    }
}
+21 −45
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@ import com.android.internal.annotations.VisibleForTesting;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;

/**
 * Reads /proc/UID/task/TID/time_in_state files to obtain statistics on CPU usage
@@ -34,10 +33,7 @@ public class SystemServerCpuThreadReader {
    private final KernelSingleProcessCpuThreadReader mKernelCpuThreadReader;
    private int[] mBinderThreadNativeTids = new int[0];  // Sorted

    private long mProcessCpuTimeUs;
    private long[] mThreadCpuTimesUs;
    private long[] mBinderThreadCpuTimesUs;
    private long mLastProcessCpuTimeUs;
    private long[] mLastProcessCpuTimeUs;
    private long[] mLastThreadCpuTimesUs;
    private long[] mLastBinderThreadCpuTimesUs;

@@ -46,14 +42,15 @@ public class SystemServerCpuThreadReader {
     */
    public static class SystemServiceCpuThreadTimes {
        // The entire process
        public long processCpuTimeUs;
        public long[] processCpuTimesUs;
        // All threads
        public long[] threadCpuTimesUs;
        // Just the threads handling incoming binder calls
        public long[] binderThreadCpuTimesUs;
    }

    private SystemServiceCpuThreadTimes mDeltaCpuThreadTimes = new SystemServiceCpuThreadTimes();
    private final SystemServiceCpuThreadTimes mDeltaCpuThreadTimes =
            new SystemServiceCpuThreadTimes();

    /**
     * Creates a configured instance of SystemServerCpuThreadReader.
@@ -83,59 +80,38 @@ public class SystemServerCpuThreadReader {
     */
    @Nullable
    public SystemServiceCpuThreadTimes readDelta() {
        int numCpuFrequencies = mKernelCpuThreadReader.getCpuFrequencyCount();
        if (mBinderThreadCpuTimesUs == null) {
            mThreadCpuTimesUs = new long[numCpuFrequencies];
            mBinderThreadCpuTimesUs = new long[numCpuFrequencies];

        final int numCpuFrequencies = mKernelCpuThreadReader.getCpuFrequencyCount();
        if (mLastProcessCpuTimeUs == null) {
            mLastProcessCpuTimeUs = new long[numCpuFrequencies];
            mLastThreadCpuTimesUs = new long[numCpuFrequencies];
            mLastBinderThreadCpuTimesUs = new long[numCpuFrequencies];

            mDeltaCpuThreadTimes.processCpuTimesUs = new long[numCpuFrequencies];
            mDeltaCpuThreadTimes.threadCpuTimesUs = new long[numCpuFrequencies];
            mDeltaCpuThreadTimes.binderThreadCpuTimesUs = new long[numCpuFrequencies];
        }

        mProcessCpuTimeUs = 0;
        Arrays.fill(mThreadCpuTimesUs, 0);
        Arrays.fill(mBinderThreadCpuTimesUs, 0);

        KernelSingleProcessCpuThreadReader.ProcessCpuUsage processCpuUsage =
                mKernelCpuThreadReader.getProcessCpuUsage();
        final KernelSingleProcessCpuThreadReader.ProcessCpuUsage processCpuUsage =
                mKernelCpuThreadReader.getProcessCpuUsage(mBinderThreadNativeTids);
        if (processCpuUsage == null) {
            return null;
        }

        mProcessCpuTimeUs = processCpuUsage.cpuTimeMillis * 1000;

        List<KernelSingleProcessCpuThreadReader.ThreadCpuUsage> threadCpuUsages =
                processCpuUsage.threadCpuUsages;
        int threadCpuUsagesSize = threadCpuUsages.size();
        for (int i = 0; i < threadCpuUsagesSize; i++) {
            KernelSingleProcessCpuThreadReader.ThreadCpuUsage tcu = threadCpuUsages.get(i);
            boolean isBinderThread =
                    Arrays.binarySearch(mBinderThreadNativeTids, tcu.threadId) >= 0;
            for (int k = 0; k < numCpuFrequencies; k++) {
                long usageTimeUs = tcu.usageTimesMillis[k] * 1000;
                mThreadCpuTimesUs[k] += usageTimeUs;
                if (isBinderThread) {
                    mBinderThreadCpuTimesUs[k] += usageTimeUs;
                }
            }
        }

        for (int i = 0; i < mThreadCpuTimesUs.length; i++) {
            mDeltaCpuThreadTimes.processCpuTimeUs =
                    Math.max(0, mProcessCpuTimeUs - mLastProcessCpuTimeUs);
        for (int i = numCpuFrequencies - 1; i >= 0; i--) {
            long processCpuTimesUs = processCpuUsage.processCpuTimesMillis[i] * 1000;
            long threadCpuTimesUs = processCpuUsage.threadCpuTimesMillis[i] * 1000;
            long binderThreadCpuTimesUs = processCpuUsage.selectedThreadCpuTimesMillis[i] * 1000;
            mDeltaCpuThreadTimes.processCpuTimesUs[i] =
                    Math.max(0, processCpuTimesUs - mLastProcessCpuTimeUs[i]);
            mDeltaCpuThreadTimes.threadCpuTimesUs[i] =
                    Math.max(0, mThreadCpuTimesUs[i] - mLastThreadCpuTimesUs[i]);
                    Math.max(0, threadCpuTimesUs - mLastThreadCpuTimesUs[i]);
            mDeltaCpuThreadTimes.binderThreadCpuTimesUs[i] =
                    Math.max(0, mBinderThreadCpuTimesUs[i] - mLastBinderThreadCpuTimesUs[i]);
            mLastThreadCpuTimesUs[i] = mThreadCpuTimesUs[i];
            mLastBinderThreadCpuTimesUs[i] = mBinderThreadCpuTimesUs[i];
                    Math.max(0, binderThreadCpuTimesUs - mLastBinderThreadCpuTimesUs[i]);
            mLastProcessCpuTimeUs[i] = processCpuTimesUs;
            mLastThreadCpuTimesUs[i] = threadCpuTimesUs;
            mLastBinderThreadCpuTimesUs[i] = binderThreadCpuTimesUs;
        }

        mLastProcessCpuTimeUs = mProcessCpuTimeUs;

        return mDeltaCpuThreadTimes;
    }
}
+7 −19
Original line number Diff line number Diff line
@@ -38,8 +38,6 @@ import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.List;

@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -59,31 +57,21 @@ public class KernelSingleProcessCpuThreadReaderTest {
    }

    @Test
    public void getThreadCpuUsage() throws IOException {
    public void getProcessCpuUsage() throws IOException {
        setupDirectory(42,
                new int[] {42, 1, 2, 3},
                new int[] {1000, 2000},
                // Units are 10ms aka 10000Us
                new int[][] {{100, 200}, {0, 200}, {100, 300}, {0, 400}},
                new int[] {1400, 1500});
                new int[][] {{100, 200}, {0, 200}, {100, 300}, {0, 600}},
                new int[] {4500, 500});

        KernelSingleProcessCpuThreadReader reader = new KernelSingleProcessCpuThreadReader(42,
                mProcDirectory.toPath());
        KernelSingleProcessCpuThreadReader.ProcessCpuUsage processCpuUsage =
                reader.getProcessCpuUsage();
        assertThat(processCpuUsage.cpuTimeMillis).isEqualTo(29000);
        List<KernelSingleProcessCpuThreadReader.ThreadCpuUsage> threadCpuUsage =
                processCpuUsage.threadCpuUsages;
        threadCpuUsage.sort(Comparator.comparingInt(o -> o.threadId));
        assertThat(threadCpuUsage).hasSize(4);
        assertThat(threadCpuUsage.get(0).threadId).isEqualTo(1);
        assertThat(threadCpuUsage.get(0).usageTimesMillis).isEqualTo(new long[]{0, 2000});
        assertThat(threadCpuUsage.get(1).threadId).isEqualTo(2);
        assertThat(threadCpuUsage.get(1).usageTimesMillis).isEqualTo(new long[]{1000, 3000});
        assertThat(threadCpuUsage.get(2).threadId).isEqualTo(3);
        assertThat(threadCpuUsage.get(2).usageTimesMillis).isEqualTo(new long[]{0, 4000});
        assertThat(threadCpuUsage.get(3).threadId).isEqualTo(42);
        assertThat(threadCpuUsage.get(3).usageTimesMillis).isEqualTo(new long[]{1000, 2000});
                reader.getProcessCpuUsage(new int[] {2, 3});
        assertThat(processCpuUsage.threadCpuTimesMillis).isEqualTo(new long[] {2000, 13000});
        assertThat(processCpuUsage.selectedThreadCpuTimesMillis).isEqualTo(new long[] {1000, 9000});
        assertThat(processCpuUsage.processCpuTimesMillis).isEqualTo(new long[] {6666, 43333});
    }

    @Test
+5 −5
Original line number Diff line number Diff line
@@ -66,7 +66,7 @@ public class SystemServicePowerCalculatorTest {
    public void testCalculateApp() {
        // Test Power Profile has two CPU clusters with 3 and 4 speeds, thus 7 freq times total
        mMockSystemServerCpuThreadReader.setCpuTimes(
                210000,
                new long[] {10000, 15000, 20000, 25000, 30000, 35000, 40000},
                new long[] {30000, 40000, 50000, 60000, 70000, 80000, 90000},
                new long[] {20000, 30000, 40000, 50000, 60000, 70000, 80000});

@@ -107,13 +107,13 @@ public class SystemServicePowerCalculatorTest {
                mMockBatteryStats.getUidStatsLocked(workSourceUid1), 0);
        mSystemServicePowerCalculator.calculateApp(app1, app1.uidObj, 0, 0,
                BatteryStats.STATS_SINCE_CHARGED);
        assertEquals(0.00018958, app1.systemServiceCpuPowerMah, 0.0000001);
        assertEquals(0.00016269, app1.systemServiceCpuPowerMah, 0.0000001);

        BatterySipper app2 = new BatterySipper(BatterySipper.DrainType.APP,
                mMockBatteryStats.getUidStatsLocked(workSourceUid2), 0);
        mSystemServicePowerCalculator.calculateApp(app2, app2.uidObj, 0, 0,
                BatteryStats.STATS_SINCE_CHARGED);
        assertEquals(0.00170625, app2.systemServiceCpuPowerMah, 0.0000001);
        assertEquals(0.00146426, app2.systemServiceCpuPowerMah, 0.0000001);
    }

    private static class MockKernelCpuUidFreqTimeReader extends
@@ -148,9 +148,9 @@ public class SystemServicePowerCalculatorTest {
            super(null);
        }

        public void setCpuTimes(long processCpuTimeUs, long[] threadCpuTimesUs,
        public void setCpuTimes(long[] processCpuTimesUs, long[] threadCpuTimesUs,
                long[] binderThreadCpuTimesUs) {
            mThreadTimes.processCpuTimeUs = processCpuTimeUs;
            mThreadTimes.processCpuTimesUs = processCpuTimesUs;
            mThreadTimes.threadCpuTimesUs = threadCpuTimesUs;
            mThreadTimes.binderThreadCpuTimesUs = binderThreadCpuTimesUs;
        }