Merge "Make KernelSingleProcessCpuThreadReader aggregate CPU times"

This commit is contained in:
Dmitri Plotnikov
2020-10-07 16:21:41 +00:00
committed by Android (Google) Code Review
5 changed files with 91 additions and 136 deletions

View File

@@ -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(": [");

View File

@@ -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;
public long[] processCpuTimesMillis;
public long[] threadCpuTimesMillis;
public long[] selectedThreadCpuTimesMillis;
ProcessCpuUsage(long cpuTimeMillis, List<ThreadCpuUsage> threadCpuUsages) {
this.cpuTimeMillis = cpuTimeMillis;
this.threadCpuUsages = threadCpuUsages;
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;
}
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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;
}