Loading core/java/com/android/internal/os/BatteryStatsImpl.java +2 −29 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -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; Loading @@ -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(": ["); Loading core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java +57 −39 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) { Loading @@ -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); Loading @@ -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 { Loading @@ -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; } } core/java/com/android/internal/os/SystemServerCpuThreadReader.java +21 −45 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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; Loading @@ -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. Loading Loading @@ -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; } } core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java +7 −19 Original line number Diff line number Diff line Loading @@ -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) Loading @@ -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 Loading core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java +5 −5 Original line number Diff line number Diff line Loading @@ -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}); Loading Loading @@ -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 Loading Loading @@ -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; } Loading Loading
core/java/com/android/internal/os/BatteryStatsImpl.java +2 −29 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -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; Loading @@ -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(": ["); Loading
core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java +57 −39 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) { Loading @@ -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); Loading @@ -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 { Loading @@ -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; } }
core/java/com/android/internal/os/SystemServerCpuThreadReader.java +21 −45 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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; Loading @@ -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. Loading Loading @@ -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; } }
core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java +7 −19 Original line number Diff line number Diff line Loading @@ -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) Loading @@ -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 Loading
core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java +5 −5 Original line number Diff line number Diff line Loading @@ -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}); Loading Loading @@ -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 Loading Loading @@ -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; } Loading