summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author TreeHugger Robot <treehugger-gerrit@google.com> 2020-12-18 21:43:43 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2020-12-18 21:43:43 +0000
commit36ca6d3452868f9076c2a76c6a3d00a31bf17440 (patch)
tree8ee8ce81d813fe141721c5527d6be846e128be0a
parentada970f4739cffccd25f8d27533c35510ee1e9c7 (diff)
parentc9e9058347e4f99f7e580869055aae039e05d8ee (diff)
Merge "Revert "Use eBPF-based time-in-state monitoring for groups of threads""
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java114
-rw-r--r--core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java293
-rw-r--r--core/java/com/android/internal/os/SystemServerCpuThreadReader.java32
-rw-r--r--core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp420
-rw-r--r--core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java131
-rw-r--r--core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java160
-rw-r--r--core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java5
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java2
8 files changed, 678 insertions, 479 deletions
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 5aedd1ea90e5..bdd96c24ade4 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -1106,6 +1106,16 @@ public class BatteryStatsImpl extends BatteryStats {
private long[] mCpuFreqs;
/**
+ * Times spent by the system server process grouped by cluster and CPU speed.
+ */
+ private LongSamplingCounterArray mSystemServerCpuTimesUs;
+
+ /**
+ * Times spent by the system server threads grouped by cluster and CPU speed.
+ */
+ private LongSamplingCounterArray mSystemServerThreadCpuTimesUs;
+
+ /**
* Times spent by the system server threads handling incoming binder requests.
*/
private LongSamplingCounterArray mBinderThreadCpuTimesUs;
@@ -10844,14 +10854,6 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
- /**
- * Starts tracking CPU time-in-state for threads of the system server process,
- * keeping a separate account of threads receiving incoming binder calls.
- */
- public void startTrackingSystemServerCpuTime() {
- mSystemServerCpuThreadReader.startTrackingThreadCpuTime();
- }
-
public void setCallback(BatteryCallback cb) {
mCallback = cb;
}
@@ -11504,6 +11506,8 @@ public class BatteryStatsImpl extends BatteryStats {
MeasuredEnergyStats.resetIfNotNull(mGlobalMeasuredEnergyStats);
+ resetIfNotNull(mSystemServerCpuTimesUs, false, elapsedRealtimeUs);
+ resetIfNotNull(mSystemServerThreadCpuTimesUs, false, elapsedRealtimeUs);
resetIfNotNull(mBinderThreadCpuTimesUs, false, elapsedRealtimeUs);
mLastHistoryStepDetails = null;
@@ -12703,17 +12707,27 @@ public class BatteryStatsImpl extends BatteryStats {
return;
}
- if (mBinderThreadCpuTimesUs == null) {
+ 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);
if (DEBUG_BINDER_STATS) {
- Slog.d(TAG, "System server threads per CPU cluster (incoming binder threads)");
+ Slog.d(TAG, "System server threads per CPU cluster (binder threads/total threads/%)");
+ long totalCpuTimeMs = 0;
+ long totalThreadTimeMs = 0;
long binderThreadTimeMs = 0;
int cpuIndex = 0;
- final long[] binderThreadCpuTimesUs = mBinderThreadCpuTimesUs.getCountsLocked(
- BatteryStats.STATS_SINCE_CHARGED);
+ final long[] systemServerCpuTimesUs =
+ mSystemServerCpuTimesUs.getCountsLocked(0);
+ final long[] systemServerThreadCpuTimesUs =
+ mSystemServerThreadCpuTimesUs.getCountsLocked(0);
+ final long[] binderThreadCpuTimesUs =
+ mBinderThreadCpuTimesUs.getCountsLocked(0);
int index = 0;
int numCpuClusters = mPowerProfile.getNumCpuClusters();
for (int cluster = 0; cluster < numCpuClusters; cluster++) {
@@ -12724,15 +12738,28 @@ public class BatteryStatsImpl extends BatteryStats {
if (speed != 0) {
sb.append(", ");
}
+ long totalCountMs = systemServerThreadCpuTimesUs[index] / 1000;
long binderCountMs = binderThreadCpuTimesUs[index] / 1000;
- sb.append(TextUtils.formatSimple("%10d", binderCountMs));
+ sb.append(String.format("%d/%d(%.1f%%)",
+ binderCountMs,
+ totalCountMs,
+ totalCountMs != 0 ? (double) binderCountMs * 100 / totalCountMs : 0));
+ totalCpuTimeMs += systemServerCpuTimesUs[index] / 1000;
+ totalThreadTimeMs += totalCountMs;
binderThreadTimeMs += binderCountMs;
index++;
}
cpuIndex += mPowerProfile.getNumCoresInCpuCluster(cluster);
Slog.d(TAG, sb.toString());
}
+
+ Slog.d(TAG, "Total system server CPU time (ms): " + totalCpuTimeMs);
+ Slog.d(TAG, "Total system server thread time (ms): " + totalThreadTimeMs);
+ Slog.d(TAG, String.format("Total Binder thread time (ms): %d (%.1f%%)",
+ binderThreadTimeMs,
+ binderThreadTimeMs != 0
+ ? (double) binderThreadTimeMs * 100 / totalThreadTimeMs : 0));
}
}
@@ -14007,16 +14034,60 @@ public class BatteryStatsImpl extends BatteryStats {
}
- /**
- * Estimates the time spent by the system server handling incoming binder requests.
- */
@Override
public long[] getSystemServiceTimeAtCpuSpeeds() {
- if (mBinderThreadCpuTimesUs == null) {
+ // Estimates the time spent by the system server handling incoming binder requests.
+ //
+ // The data that we can get from the kernel is this:
+ // - CPU duration for a (thread - cluster - CPU speed) combination
+ // - CPU duration for a (UID - cluster - CPU speed) combination
+ //
+ // The configuration we have in the Power Profile is this:
+ // - Average CPU power for a (cluster - CPU speed) combination.
+ //
+ // The model used by BatteryStats can be illustrated with this example:
+ //
+ // - Let's say the system server has 10 threads.
+ // - These 10 threads spent 1000 ms of CPU time in aggregate
+ // - Of the 10 threads 4 were execute exclusively incoming binder calls.
+ // - These 4 "binder" threads consumed 600 ms of CPU time in aggregate
+ // - The real time spent by the system server process doing all of this is, say, 200 ms.
+ //
+ // We will assume that power consumption is proportional to the time spent by the CPU
+ // across all threads. This is a crude assumption, but we don't have more detailed data.
+ // Thus,
+ // binderRealTime = realTime * aggregateBinderThreadTime / aggregateAllThreadTime
+ //
+ // In our example,
+ // binderRealTime = 200 * 600 / 1000 = 120ms
+ //
+ // We can then multiply this estimated time by the average power to obtain an estimate
+ // of the total power consumed by incoming binder calls for the given cluster/speed
+ // combination.
+
+ if (mSystemServerCpuTimesUs == null) {
return null;
}
- return mBinderThreadCpuTimesUs.getCountsLocked(BatteryStats.STATS_SINCE_CHARGED);
+ final long[] systemServerCpuTimesUs = mSystemServerCpuTimesUs.getCountsLocked(
+ BatteryStats.STATS_SINCE_CHARGED);
+ final long [] systemServerThreadCpuTimesUs = mSystemServerThreadCpuTimesUs.getCountsLocked(
+ BatteryStats.STATS_SINCE_CHARGED);
+ final long[] binderThreadCpuTimesUs = mBinderThreadCpuTimesUs.getCountsLocked(
+ BatteryStats.STATS_SINCE_CHARGED);
+
+ final int size = systemServerCpuTimesUs.length;
+ final long[] results = new long[size];
+
+ for (int i = 0; i < size; i++) {
+ if (systemServerThreadCpuTimesUs[i] == 0) {
+ continue;
+ }
+
+ results[i] = systemServerCpuTimesUs[i] * binderThreadCpuTimesUs[i]
+ / systemServerThreadCpuTimesUs[i];
+ }
+ return results;
}
/**
@@ -14412,7 +14483,7 @@ public class BatteryStatsImpl extends BatteryStats {
}
updateSystemServiceCallStats();
- if (mBinderThreadCpuTimesUs != null) {
+ if (mSystemServerThreadCpuTimesUs != null) {
pw.println("Per UID System server binder time in ms:");
long[] systemServiceTimeAtCpuSpeeds = getSystemServiceTimeAtCpuSpeeds();
for (int i = 0; i < size; i++) {
@@ -15999,6 +16070,9 @@ public class BatteryStatsImpl extends BatteryStats {
mUidStats.append(uid, u);
}
+ mSystemServerCpuTimesUs = LongSamplingCounterArray.readFromParcel(in, mOnBatteryTimeBase);
+ mSystemServerThreadCpuTimesUs = LongSamplingCounterArray.readFromParcel(in,
+ mOnBatteryTimeBase);
mBinderThreadCpuTimesUs = LongSamplingCounterArray.readFromParcel(in, mOnBatteryTimeBase);
}
@@ -16207,6 +16281,8 @@ public class BatteryStatsImpl extends BatteryStats {
} else {
out.writeInt(0);
}
+ LongSamplingCounterArray.writeToParcel(out, mSystemServerCpuTimesUs);
+ LongSamplingCounterArray.writeToParcel(out, mSystemServerThreadCpuTimesUs);
LongSamplingCounterArray.writeToParcel(out, mBinderThreadCpuTimesUs);
}
diff --git a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
index 4d2a08a4bcf3..e6a962312a00 100644
--- a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
+++ b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
@@ -16,12 +16,23 @@
package com.android.internal.os;
+import static android.os.Process.PROC_OUT_LONG;
+import static android.os.Process.PROC_SPACE_TERM;
+
import android.annotation.Nullable;
+import android.os.Process;
+import android.system.Os;
+import android.system.OsConstants;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import java.io.IOException;
+import java.nio.file.DirectoryIteratorException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Arrays;
/**
@@ -34,65 +45,93 @@ public class KernelSingleProcessCpuThreadReader {
private static final String TAG = "KernelSingleProcCpuThreadRdr";
private static final boolean DEBUG = false;
+ private static final boolean NATIVE_ENABLED = true;
+
+ /**
+ * The name of the file to read CPU statistics from, must be found in {@code
+ * /proc/$PID/task/$TID}
+ */
+ private static final String CPU_STATISTICS_FILENAME = "time_in_state";
+
+ private static final String PROC_STAT_FILENAME = "stat";
+
+ /** Directory under /proc/$PID containing CPU stats files for threads */
+ public static final String THREAD_CPU_STATS_DIRECTORY = "task";
+
+ /** Default mount location of the {@code proc} filesystem */
+ private static final Path DEFAULT_PROC_PATH = Paths.get("/proc");
+
+ /** The initial {@code time_in_state} file for {@link ProcTimeInStateReader} */
+ private static final Path INITIAL_TIME_IN_STATE_PATH = Paths.get("self/time_in_state");
+
+ /** See https://man7.org/linux/man-pages/man5/proc.5.html */
+ private static final int[] PROCESS_FULL_STATS_FORMAT = new int[] {
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM | PROC_OUT_LONG, // 14: utime
+ PROC_SPACE_TERM | PROC_OUT_LONG, // 15: stime
+ // Ignore remaining fields
+ };
+
+ private final long[] mProcessFullStatsData = new long[2];
+
+ private static final int PROCESS_FULL_STAT_UTIME = 0;
+ private static final int PROCESS_FULL_STAT_STIME = 1;
+
+ /** Used to read and parse {@code time_in_state} files */
+ private final ProcTimeInStateReader mProcTimeInStateReader;
private final int mPid;
- private final CpuTimeInStateReader mCpuTimeInStateReader;
+ /** Where the proc filesystem is mounted */
+ private final Path mProcPath;
- private int[] mSelectedThreadNativeTids = new int[0]; // Sorted
+ // How long a CPU jiffy is in milliseconds.
+ private final long mJiffyMillis;
- /**
- * Count of frequencies read from the {@code time_in_state} file.
- */
- private int mFrequencyCount;
+ // Path: /proc/<pid>/stat
+ private final String mProcessStatFilePath;
- private boolean mIsTracking;
+ // Path: /proc/<pid>/task
+ private final Path mThreadsDirectoryPath;
/**
- * A CPU time-in-state provider for testing. Imitates the behavior of the corresponding
- * methods in frameworks/native/libs/cputimeinstate/cputimeinstate.c
+ * Count of frequencies read from the {@code time_in_state} file. Read from {@link
+ * #mProcTimeInStateReader#getCpuFrequenciesKhz()}.
*/
- @VisibleForTesting
- public interface CpuTimeInStateReader {
- /**
- * Returns the overall number of cluster-frequency combinations.
- */
- int getCpuFrequencyCount();
-
- /**
- * Returns true to indicate success.
- *
- * Called from native.
- */
- boolean startTrackingProcessCpuTimes(int tgid);
-
- /**
- * Returns true to indicate success.
- *
- * Called from native.
- */
- boolean startAggregatingTaskCpuTimes(int pid, int aggregationKey);
-
- /**
- * Must return an array of strings formatted like this:
- * "aggKey:t0_0 t0_1...:t1_0 t1_1..."
- * Times should be provided in nanoseconds.
- *
- * Called from native.
- */
- String[] getAggregatedTaskCpuFreqTimes(int pid);
- }
+ private int mFrequencyCount;
/**
* Create with a path where `proc` is mounted. Used primarily for testing
*
* @param pid PID of the process whose threads are to be read.
+ * @param procPath where `proc` is mounted (to find, see {@code mount | grep ^proc})
*/
@VisibleForTesting
- public KernelSingleProcessCpuThreadReader(int pid,
- @Nullable CpuTimeInStateReader cpuTimeInStateReader) throws IOException {
+ public KernelSingleProcessCpuThreadReader(
+ int pid,
+ Path procPath) throws IOException {
mPid = pid;
- mCpuTimeInStateReader = cpuTimeInStateReader;
+ mProcPath = procPath;
+ mProcTimeInStateReader = new ProcTimeInStateReader(
+ mProcPath.resolve(INITIAL_TIME_IN_STATE_PATH));
+ long jiffyHz = Os.sysconf(OsConstants._SC_CLK_TCK);
+ mJiffyMillis = 1000 / jiffyHz;
+ mProcessStatFilePath =
+ mProcPath.resolve(String.valueOf(mPid)).resolve(PROC_STAT_FILENAME).toString();
+ mThreadsDirectoryPath =
+ mProcPath.resolve(String.valueOf(mPid)).resolve(THREAD_CPU_STATS_DIRECTORY);
}
/**
@@ -103,7 +142,7 @@ public class KernelSingleProcessCpuThreadReader {
@Nullable
public static KernelSingleProcessCpuThreadReader create(int pid) {
try {
- return new KernelSingleProcessCpuThreadReader(pid, null);
+ return new KernelSingleProcessCpuThreadReader(pid, DEFAULT_PROC_PATH);
} catch (IOException e) {
Slog.e(TAG, "Failed to initialize KernelSingleProcessCpuThreadReader", e);
return null;
@@ -111,98 +150,146 @@ public class KernelSingleProcessCpuThreadReader {
}
/**
- * Starts tracking aggregated CPU time-in-state of all threads of the process with the PID
- * supplied in the constructor.
- */
- public void startTrackingThreadCpuTimes() {
- if (!mIsTracking) {
- if (!startTrackingProcessCpuTimes(mPid, mCpuTimeInStateReader)) {
- Slog.e(TAG, "Failed to start tracking process CPU times for " + mPid);
- }
- if (mSelectedThreadNativeTids.length > 0) {
- if (!startAggregatingThreadCpuTimes(mSelectedThreadNativeTids,
- mCpuTimeInStateReader)) {
- Slog.e(TAG, "Failed to start tracking aggregated thread CPU times for "
- + Arrays.toString(mSelectedThreadNativeTids));
- }
- }
- mIsTracking = true;
- }
- }
-
- /**
- * @param nativeTids an 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.
- */
- public void setSelectedThreadIds(int[] nativeTids) {
- mSelectedThreadNativeTids = nativeTids.clone();
- if (mIsTracking) {
- startAggregatingThreadCpuTimes(mSelectedThreadNativeTids, mCpuTimeInStateReader);
- }
- }
-
- /**
- * Get the CPU frequencies that correspond to the times reported in {@link ProcessCpuUsage}.
+ * Get the CPU frequencies that correspond to the times reported in {@link
+ * ProcessCpuUsage#processCpuTimesMillis} etc.
*/
public int getCpuFrequencyCount() {
if (mFrequencyCount == 0) {
- mFrequencyCount = getCpuFrequencyCount(mCpuTimeInStateReader);
+ mFrequencyCount = mProcTimeInStateReader.getFrequenciesKhz().length;
}
return mFrequencyCount;
}
/**
- * Get the total CPU usage of the process with the PID specified in the
- * constructor. The CPU usage time is aggregated across all threads and may
- * exceed the time the entire process has been running.
+ * 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 for PID " + mPid);
+ Slog.d(TAG, "Reading CPU thread usages with directory " + mProcPath + " process ID "
+ + mPid);
+ }
+
+ int cpuFrequencyCount = getCpuFrequencyCount();
+ ProcessCpuUsage processCpuUsage = new ProcessCpuUsage(cpuFrequencyCount);
+
+ if (NATIVE_ENABLED) {
+ boolean result = readProcessCpuUsage(mProcPath.toString(), mPid,
+ selectedThreadIds, processCpuUsage.processCpuTimesMillis,
+ processCpuUsage.threadCpuTimesMillis,
+ processCpuUsage.selectedThreadCpuTimesMillis);
+ if (!result) {
+ return null;
+ }
+ return processCpuUsage;
}
- ProcessCpuUsage processCpuUsage = new ProcessCpuUsage(getCpuFrequencyCount());
+ if (!isSorted(selectedThreadIds)) {
+ throw new IllegalArgumentException("selectedThreadIds is not sorted: "
+ + Arrays.toString(selectedThreadIds));
+ }
- boolean result = readProcessCpuUsage(mPid,
- processCpuUsage.threadCpuTimesMillis,
- processCpuUsage.selectedThreadCpuTimesMillis,
- mCpuTimeInStateReader);
- if (!result) {
+ if (!Process.readProcFile(mProcessStatFilePath, PROCESS_FULL_STATS_FORMAT, null,
+ mProcessFullStatsData, null)) {
+ Slog.e(TAG, "Failed to read process stat file " + mProcessStatFilePath);
return null;
}
- if (DEBUG) {
- Slog.d(TAG, "threadCpuTimesMillis = "
- + Arrays.toString(processCpuUsage.threadCpuTimesMillis));
- Slog.d(TAG, "selectedThreadCpuTimesMillis = "
- + Arrays.toString(processCpuUsage.selectedThreadCpuTimesMillis));
+ long utime = mProcessFullStatsData[PROCESS_FULL_STAT_UTIME];
+ long stime = mProcessFullStatsData[PROCESS_FULL_STAT_STIME];
+
+ long processCpuTimeMillis = (utime + stime) * mJiffyMillis;
+
+ try (DirectoryStream<Path> threadPaths = Files.newDirectoryStream(mThreadsDirectoryPath)) {
+ for (Path threadDirectory : threadPaths) {
+ readThreadCpuUsage(processCpuUsage, selectedThreadIds, threadDirectory);
+ }
+ } catch (IOException | DirectoryIteratorException e) {
+ // Expected when a process finishes
+ 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];
+ }
+
+ for (int i = cpuFrequencyCount - 1; i >= 0; i--) {
+ processCpuUsage.processCpuTimesMillis[i] =
+ processCpuTimeMillis * processCpuUsage.threadCpuTimesMillis[i]
+ / totalCpuTimeAllThreads;
}
return processCpuUsage;
}
+ /**
+ * Reads a thread's CPU usage and aggregates the per-cluster per-frequency CPU times.
+ *
+ * @param threadDirectory the {@code /proc} directory of the thread
+ */
+ private void readThreadCpuUsage(ProcessCpuUsage processCpuUsage, int[] selectedThreadIds,
+ Path threadDirectory) {
+ // Get the thread ID from the directory name
+ final int threadId;
+ try {
+ final String directoryName = threadDirectory.getFileName().toString();
+ threadId = Integer.parseInt(directoryName);
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Failed to parse thread ID when iterating over /proc/*/task", e);
+ 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;
+ }
+
+ 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, all of its threads and a selected subset of its threads */
public static class ProcessCpuUsage {
+ 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];
}
}
- private native int getCpuFrequencyCount(CpuTimeInStateReader reader);
-
- private native boolean startTrackingProcessCpuTimes(int pid, CpuTimeInStateReader reader);
-
- private native boolean startAggregatingThreadCpuTimes(int[] selectedThreadIds,
- CpuTimeInStateReader reader);
+ 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;
+ }
- private native boolean readProcessCpuUsage(int pid,
- long[] threadCpuTimesMillis,
- long[] selectedThreadCpuTimesMillis,
- CpuTimeInStateReader reader);
+ private native boolean readProcessCpuUsage(String procPath, int pid, int[] selectedThreadIds,
+ long[] processCpuTimesMillis, long[] threadCpuTimesMillis,
+ long[] selectedThreadCpuTimesMillis);
}
diff --git a/core/java/com/android/internal/os/SystemServerCpuThreadReader.java b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
index fbad75e93a17..fbbee94feacc 100644
--- a/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
+++ b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
@@ -22,6 +22,8 @@ import android.os.Process;
import com.android.internal.annotations.VisibleForTesting;
import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Arrays;
/**
* Reads /proc/UID/task/TID/time_in_state files to obtain statistics on CPU usage
@@ -29,7 +31,9 @@ import java.io.IOException;
*/
public class SystemServerCpuThreadReader {
private final KernelSingleProcessCpuThreadReader mKernelCpuThreadReader;
+ private int[] mBinderThreadNativeTids = new int[0]; // Sorted
+ private long[] mLastProcessCpuTimeUs;
private long[] mLastThreadCpuTimesUs;
private long[] mLastBinderThreadCpuTimesUs;
@@ -37,6 +41,8 @@ public class SystemServerCpuThreadReader {
* Times (in microseconds) spent by the system server UID.
*/
public static class SystemServiceCpuThreadTimes {
+ // The entire process
+ public long[] processCpuTimesUs;
// All threads
public long[] threadCpuTimesUs;
// Just the threads handling incoming binder calls
@@ -55,10 +61,8 @@ public class SystemServerCpuThreadReader {
}
@VisibleForTesting
- public SystemServerCpuThreadReader(int pid,
- KernelSingleProcessCpuThreadReader.CpuTimeInStateReader cpuTimeInStateReader)
- throws IOException {
- this(new KernelSingleProcessCpuThreadReader(pid, cpuTimeInStateReader));
+ public SystemServerCpuThreadReader(Path procPath, int pid) throws IOException {
+ this(new KernelSingleProcessCpuThreadReader(pid, procPath));
}
@VisibleForTesting
@@ -66,15 +70,9 @@ public class SystemServerCpuThreadReader {
mKernelCpuThreadReader = kernelCpuThreadReader;
}
- /**
- * Start tracking CPU time-in-state for the process specified in the constructor.
- */
- public void startTrackingThreadCpuTime() {
- mKernelCpuThreadReader.startTrackingThreadCpuTimes();
- }
-
public void setBinderThreadNativeTids(int[] nativeTids) {
- mKernelCpuThreadReader.setSelectedThreadIds(nativeTids);
+ mBinderThreadNativeTids = nativeTids.clone();
+ Arrays.sort(mBinderThreadNativeTids);
}
/**
@@ -83,27 +81,33 @@ public class SystemServerCpuThreadReader {
@Nullable
public SystemServiceCpuThreadTimes readDelta() {
final int numCpuFrequencies = mKernelCpuThreadReader.getCpuFrequencyCount();
- if (mLastThreadCpuTimesUs == null) {
+ 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];
}
final KernelSingleProcessCpuThreadReader.ProcessCpuUsage processCpuUsage =
- mKernelCpuThreadReader.getProcessCpuUsage();
+ mKernelCpuThreadReader.getProcessCpuUsage(mBinderThreadNativeTids);
if (processCpuUsage == null) {
return null;
}
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, threadCpuTimesUs - mLastThreadCpuTimesUs[i]);
mDeltaCpuThreadTimes.binderThreadCpuTimesUs[i] =
Math.max(0, binderThreadCpuTimesUs - mLastBinderThreadCpuTimesUs[i]);
+ mLastProcessCpuTimeUs[i] = processCpuTimesUs;
mLastThreadCpuTimesUs[i] = threadCpuTimesUs;
mLastBinderThreadCpuTimesUs[i] = binderThreadCpuTimesUs;
}
diff --git a/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp b/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp
index dfae68429f6d..52bed6bcfce3 100644
--- a/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp
+++ b/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp
@@ -26,308 +26,244 @@
#include <android_runtime/Log.h>
#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedUtfChars.h>
namespace android {
-static constexpr uint16_t DEFAULT_THREAD_AGGREGATION_KEY = 0;
-static constexpr uint16_t SELECTED_THREAD_AGGREGATION_KEY = 1;
-
-static constexpr uint64_t NSEC_PER_MSEC = 1000000;
-
// Number of milliseconds in a jiffy - the unit of time measurement for processes and threads
static const uint32_t gJiffyMillis = (uint32_t)(1000 / sysconf(_SC_CLK_TCK));
-// Abstract class for readers of CPU time-in-state. There are two implementations of
-// this class: BpfCpuTimeInStateReader and MockCpuTimeInStateReader. The former is used
-// by the production code. The latter is used by unit tests to provide mock
-// CPU time-in-state data via a Java implementation.
-class ICpuTimeInStateReader {
-public:
- virtual ~ICpuTimeInStateReader() {}
-
- // Returns the overall number of cluser-frequency combinations
- virtual size_t getCpuFrequencyCount();
-
- // Marks the CPU time-in-state tracking for threads of the specified TGID
- virtual bool startTrackingProcessCpuTimes(pid_t) = 0;
-
- // Marks the thread specified by its PID for CPU time-in-state tracking.
- virtual bool startAggregatingTaskCpuTimes(pid_t, uint16_t) = 0;
-
- // Retrieves the accumulated time-in-state data, which is organized as a map
- // from aggregation keys to vectors of vectors using the format:
- // { aggKey0 -> [[t0_0_0, t0_0_1, ...], [t0_1_0, t0_1_1, ...], ...],
- // aggKey1 -> [[t1_0_0, t1_0_1, ...], [t1_1_0, t1_1_1, ...], ...], ... }
- // where ti_j_k is the ns tid i spent running on the jth cluster at the cluster's kth lowest
- // freq.
- virtual std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
- getAggregatedTaskCpuFreqTimes(pid_t, const std::vector<uint16_t> &);
-};
-
-// ICpuTimeInStateReader that uses eBPF to provide a map of aggregated CPU time-in-state values.
-// See cputtimeinstate.h/.cpp
-class BpfCpuTimeInStateReader : public ICpuTimeInStateReader {
-public:
- size_t getCpuFrequencyCount() {
- std::optional<std::vector<std::vector<uint32_t>>> cpuFreqs = android::bpf::getCpuFreqs();
- if (!cpuFreqs) {
- ALOGE("Cannot obtain CPU frequency count");
- return 0;
- }
-
- size_t freqCount = 0;
- for (auto cluster : *cpuFreqs) {
- freqCount += cluster.size();
- }
-
- return freqCount;
- }
-
- bool startTrackingProcessCpuTimes(pid_t tgid) {
- return android::bpf::startTrackingProcessCpuTimes(tgid);
- }
-
- bool startAggregatingTaskCpuTimes(pid_t pid, uint16_t aggregationKey) {
- return android::bpf::startAggregatingTaskCpuTimes(pid, aggregationKey);
- }
+// Given a PID, returns a vector of all TIDs for the process' tasks. Thread IDs are
+// file names in the /proc/<pid>/task directory.
+static bool getThreadIds(const std::string &procPath, const pid_t pid,
+ std::vector<pid_t> &outThreadIds) {
+ std::string taskPath = android::base::StringPrintf("%s/%u/task", procPath.c_str(), pid);
- std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
- getAggregatedTaskCpuFreqTimes(pid_t pid, const std::vector<uint16_t> &aggregationKeys) {
- return android::bpf::getAggregatedTaskCpuFreqTimes(pid, aggregationKeys);
+ struct dirent **dirlist;
+ int threadCount = scandir(taskPath.c_str(), &dirlist, NULL, NULL);
+ if (threadCount == -1) {
+ ALOGE("Cannot read directory %s", taskPath.c_str());
+ return false;
}
-};
-
-// ICpuTimeInStateReader that uses JNI to provide a map of aggregated CPU time-in-state
-// values.
-// This version of CpuTimeInStateReader is used exclusively for providing mock data in tests.
-class MockCpuTimeInStateReader : public ICpuTimeInStateReader {
-private:
- JNIEnv *mEnv;
- jobject mCpuTimeInStateReader;
-
-public:
- MockCpuTimeInStateReader(JNIEnv *env, jobject cpuTimeInStateReader)
- : mEnv(env), mCpuTimeInStateReader(cpuTimeInStateReader) {}
-
- size_t getCpuFrequencyCount();
-
- bool startTrackingProcessCpuTimes(pid_t tgid);
- bool startAggregatingTaskCpuTimes(pid_t pid, uint16_t aggregationKey);
+ outThreadIds.reserve(threadCount);
- std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
- getAggregatedTaskCpuFreqTimes(pid_t tgid, const std::vector<uint16_t> &aggregationKeys);
-};
-
-static ICpuTimeInStateReader *getCpuTimeInStateReader(JNIEnv *env,
- jobject cpuTimeInStateReaderObject) {
- if (cpuTimeInStateReaderObject) {
- return new MockCpuTimeInStateReader(env, cpuTimeInStateReaderObject);
- } else {
- return new BpfCpuTimeInStateReader();
+ for (int i = 0; i < threadCount; i++) {
+ pid_t tid;
+ if (android::base::ParseInt<pid_t>(dirlist[i]->d_name, &tid)) {
+ outThreadIds.push_back(tid);
+ }
+ free(dirlist[i]);
}
-}
+ free(dirlist);
-static jint getCpuFrequencyCount(JNIEnv *env, jclass, jobject cpuTimeInStateReaderObject) {
- std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
- getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
- return cpuTimeInStateReader->getCpuFrequencyCount();
+ return true;
}
-static jboolean startTrackingProcessCpuTimes(JNIEnv *env, jclass, jint tgid,
- jobject cpuTimeInStateReaderObject) {
- std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
- getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
- return cpuTimeInStateReader->startTrackingProcessCpuTimes(tgid);
-}
+// Reads contents of a time_in_state file and returns times as a vector of times per frequency
+// A time_in_state file contains pairs of frequency - time (in jiffies):
+//
+// cpu0
+// 300000 30
+// 403200 0
+// cpu4
+// 710400 10
+// 825600 20
+// 940800 30
+//
+static bool getThreadTimeInState(const std::string &procPath, const pid_t pid, const pid_t tid,
+ const size_t frequencyCount,
+ std::vector<uint64_t> &outThreadTimeInState) {
+ std::string timeInStateFilePath =
+ android::base::StringPrintf("%s/%u/task/%u/time_in_state", procPath.c_str(), pid, tid);
+ std::string data;
+
+ if (!android::base::ReadFileToString(timeInStateFilePath, &data)) {
+ ALOGE("Cannot read file: %s", timeInStateFilePath.c_str());
+ return false;
+ }
-static jboolean startAggregatingThreadCpuTimes(JNIEnv *env, jclass, jintArray selectedThreadIdArray,
- jobject cpuTimeInStateReaderObject) {
- ScopedIntArrayRO selectedThreadIds(env, selectedThreadIdArray);
- std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
- getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
+ auto lines = android::base::Split(data, "\n");
+ size_t index = 0;
+ for (const auto &line : lines) {
+ if (line.empty()) {
+ continue;
+ }
- for (int i = 0; i < selectedThreadIds.size(); i++) {
- if (!cpuTimeInStateReader->startAggregatingTaskCpuTimes(selectedThreadIds[i],
- SELECTED_THREAD_AGGREGATION_KEY)) {
+ auto numbers = android::base::Split(line, " ");
+ if (numbers.size() != 2) {
+ continue;
+ }
+ uint64_t timeInState;
+ if (!android::base::ParseUint<uint64_t>(numbers[1], &timeInState)) {
+ ALOGE("Invalid time_in_state file format: %s", timeInStateFilePath.c_str());
return false;
}
- }
- return true;
-}
-
-// Converts time-in-state data from a vector of vectors to a flat array.
-// Also converts from nanoseconds to milliseconds.
-static bool flattenTimeInStateData(ScopedLongArrayRW &cpuTimesMillis,
- const std::vector<std::vector<uint64_t>> &data) {
- size_t frequencyCount = cpuTimesMillis.size();
- size_t index = 0;
- for (const auto &cluster : data) {
- for (const uint64_t &timeNanos : cluster) {
- if (index < frequencyCount) {
- cpuTimesMillis[index] = timeNanos / NSEC_PER_MSEC;
- }
- index++;
+ if (index < frequencyCount) {
+ outThreadTimeInState[index] = timeInState;
}
+ index++;
}
+
if (index != frequencyCount) {
- ALOGE("CPU time-in-state reader returned data for %zu frequencies; expected: %zu", index,
- frequencyCount);
+ ALOGE("Incorrect number of frequencies %u in %s. Expected %u",
+ (uint32_t)outThreadTimeInState.size(), timeInStateFilePath.c_str(),
+ (uint32_t)frequencyCount);
return false;
}
return true;
}
-// Reads all CPU time-in-state data accumulated by BPF and aggregates per-frequency
-// time in state data for all threads. Also, separately aggregates time in state for
-// selected threads whose TIDs are passes as selectedThreadIds.
-static jboolean readProcessCpuUsage(JNIEnv *env, jclass, jint pid,
- jlongArray threadCpuTimesMillisArray,
- jlongArray selectedThreadCpuTimesMillisArray,
- jobject cpuTimeInStateReaderObject) {
- ScopedLongArrayRW threadCpuTimesMillis(env, threadCpuTimesMillisArray);
- ScopedLongArrayRW selectedThreadCpuTimesMillis(env, selectedThreadCpuTimesMillisArray);
- std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
- getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
+static int pidCompare(const void *a, const void *b) {
+ return (*(pid_t *)a - *(pid_t *)b);
+}
- const size_t frequencyCount = cpuTimeInStateReader->getCpuFrequencyCount();
+static inline bool isSelectedThread(const pid_t tid, const pid_t *selectedThreadIds,
+ const size_t selectedThreadCount) {
+ return bsearch(&tid, selectedThreadIds, selectedThreadCount, sizeof(pid_t), pidCompare) != NULL;
+}
- if (threadCpuTimesMillis.size() != frequencyCount) {
- ALOGE("Invalid threadCpuTimesMillis array length: %zu frequencies; expected: %zu",
- threadCpuTimesMillis.size(), frequencyCount);
- return false;
+// Reads all /proc/<pid>/task/*/time_in_state files and aggregates per-frequency
+// time in state data for all threads. Also, separately aggregates time in state for
+// selected threads whose TIDs are passes as selectedThreadIds.
+static void aggregateThreadCpuTimes(const std::string &procPath, const pid_t pid,
+ const std::vector<pid_t> &threadIds,
+ const size_t frequencyCount, const pid_t *selectedThreadIds,
+ const size_t selectedThreadCount,
+ uint64_t *threadCpuTimesMillis,
+ uint64_t *selectedThreadCpuTimesMillis) {
+ for (size_t j = 0; j < frequencyCount; j++) {
+ threadCpuTimesMillis[j] = 0;
+ selectedThreadCpuTimesMillis[j] = 0;
}
- if (selectedThreadCpuTimesMillis.size() != frequencyCount) {
- ALOGE("Invalid selectedThreadCpuTimesMillis array length: %zu frequencies; expected: %zu",
- selectedThreadCpuTimesMillis.size(), frequencyCount);
- return false;
- }
+ for (size_t i = 0; i < threadIds.size(); i++) {
+ pid_t tid = threadIds[i];
+ std::vector<uint64_t> timeInState(frequencyCount);
+ if (!getThreadTimeInState(procPath, pid, tid, frequencyCount, timeInState)) {
+ continue;
+ }
+ bool selectedThread = isSelectedThread(tid, selectedThreadIds, selectedThreadCount);
+ for (size_t j = 0; j < frequencyCount; j++) {
+ threadCpuTimesMillis[j] += timeInState[j];
+ if (selectedThread) {
+ selectedThreadCpuTimesMillis[j] += timeInState[j];
+ }
+ }
+ }
for (size_t i = 0; i < frequencyCount; i++) {
- threadCpuTimesMillis[i] = 0;
- selectedThreadCpuTimesMillis[i] = 0;
+ threadCpuTimesMillis[i] *= gJiffyMillis;
+ selectedThreadCpuTimesMillis[i] *= gJiffyMillis;
}
+}
- std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>> data =
- cpuTimeInStateReader->getAggregatedTaskCpuFreqTimes(pid,
- {DEFAULT_THREAD_AGGREGATION_KEY,
- SELECTED_THREAD_AGGREGATION_KEY});
- if (!data) {
- ALOGE("Cannot read thread CPU times for PID %d", pid);
+// Reads process utime and stime from the /proc/<pid>/stat file.
+// Format of this file is described in https://man7.org/linux/man-pages/man5/proc.5.html.
+static bool getProcessCpuTime(const std::string &procPath, const pid_t pid,
+ uint64_t &outTimeMillis) {
+ std::string statFilePath = android::base::StringPrintf("%s/%u/stat", procPath.c_str(), pid);
+ std::string data;
+ if (!android::base::ReadFileToString(statFilePath, &data)) {
return false;
}
- if (!flattenTimeInStateData(threadCpuTimesMillis, (*data)[DEFAULT_THREAD_AGGREGATION_KEY])) {
- return false;
- }
+ auto fields = android::base::Split(data, " ");
+ uint64_t utime, stime;
- if (!flattenTimeInStateData(selectedThreadCpuTimesMillis,
- (*data)[SELECTED_THREAD_AGGREGATION_KEY])) {
+ // Field 14 (counting from 1) is utime - process time in user space, in jiffies
+ // Field 15 (counting from 1) is stime - process time in system space, in jiffies
+ if (fields.size() < 15 || !android::base::ParseUint(fields[13], &utime) ||
+ !android::base::ParseUint(fields[14], &stime)) {
+ ALOGE("Invalid file format %s", statFilePath.c_str());
return false;
}
- // threadCpuTimesMillis returns CPU times for _all_ threads, including the selected ones
+ outTimeMillis = (utime + stime) * gJiffyMillis;
+ return true;
+}
+
+// Estimates 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
+static void estimateProcessTimeInState(const uint64_t processCpuTimeMillis,
+ const uint64_t *threadCpuTimesMillis,
+ const size_t frequencyCount,
+ uint64_t *processCpuTimesMillis) {
+ uint64_t totalCpuTimeAllThreads = 0;
for (size_t i = 0; i < frequencyCount; i++) {
- threadCpuTimesMillis[i] += selectedThreadCpuTimesMillis[i];
+ totalCpuTimeAllThreads += threadCpuTimesMillis[i];
}
- return true;
+ if (totalCpuTimeAllThreads != 0) {
+ for (size_t i = 0; i < frequencyCount; i++) {
+ processCpuTimesMillis[i] =
+ processCpuTimeMillis * threadCpuTimesMillis[i] / totalCpuTimeAllThreads;
+ }
+ } else {
+ for (size_t i = 0; i < frequencyCount; i++) {
+ processCpuTimesMillis[i] = 0;
+ }
+ }
}
-static const JNINativeMethod g_single_methods[] = {
- {"getCpuFrequencyCount",
- "(Lcom/android/internal/os/KernelSingleProcessCpuThreadReader$CpuTimeInStateReader;)I",
- (void *)getCpuFrequencyCount},
- {"startTrackingProcessCpuTimes",
- "(ILcom/android/internal/os/KernelSingleProcessCpuThreadReader$CpuTimeInStateReader;)Z",
- (void *)startTrackingProcessCpuTimes},
- {"startAggregatingThreadCpuTimes",
- "([ILcom/android/internal/os/KernelSingleProcessCpuThreadReader$CpuTimeInStateReader;)Z",
- (void *)startAggregatingThreadCpuTimes},
- {"readProcessCpuUsage",
- "(I[J[J"
- "Lcom/android/internal/os/KernelSingleProcessCpuThreadReader$CpuTimeInStateReader;)Z",
- (void *)readProcessCpuUsage},
-};
+static jboolean readProcessCpuUsage(JNIEnv *env, jclass, jstring procPath, jint pid,
+ jintArray selectedThreadIdArray,
+ jlongArray processCpuTimesMillisArray,
+ jlongArray threadCpuTimesMillisArray,
+ jlongArray selectedThreadCpuTimesMillisArray) {
+ ScopedUtfChars procPathChars(env, procPath);
+ ScopedIntArrayRO selectedThreadIds(env, selectedThreadIdArray);
+ ScopedLongArrayRW processCpuTimesMillis(env, processCpuTimesMillisArray);
+ ScopedLongArrayRW threadCpuTimesMillis(env, threadCpuTimesMillisArray);
+ ScopedLongArrayRW selectedThreadCpuTimesMillis(env, selectedThreadCpuTimesMillisArray);
-int register_com_android_internal_os_KernelSingleProcessCpuThreadReader(JNIEnv *env) {
- return RegisterMethodsOrDie(env, "com/android/internal/os/KernelSingleProcessCpuThreadReader",
- g_single_methods, NELEM(g_single_methods));
-}
+ std::string procPathStr(procPathChars.c_str());
-size_t MockCpuTimeInStateReader::getCpuFrequencyCount() {
- jclass cls = mEnv->GetObjectClass(mCpuTimeInStateReader);
- jmethodID mid = mEnv->GetMethodID(cls, "getCpuFrequencyCount", "()I");
- if (mid == 0) {
- ALOGE("Couldn't find the method getCpuFrequencyCount");
+ // Get all thread IDs for the process.
+ std::vector<pid_t> threadIds;
+ if (!getThreadIds(procPathStr, pid, threadIds)) {
+ ALOGE("Could not obtain thread IDs from: %s", procPathStr.c_str());
return false;
}
- return (size_t)mEnv->CallIntMethod(mCpuTimeInStateReader, mid);
-}
-bool MockCpuTimeInStateReader::startTrackingProcessCpuTimes(pid_t tgid) {
- jclass cls = mEnv->GetObjectClass(mCpuTimeInStateReader);
- jmethodID mid = mEnv->GetMethodID(cls, "startTrackingProcessCpuTimes", "(I)Z");
- if (mid == 0) {
- ALOGE("Couldn't find the method startTrackingProcessCpuTimes");
+ size_t frequencyCount = processCpuTimesMillis.size();
+
+ if (threadCpuTimesMillis.size() != frequencyCount) {
+ ALOGE("Invalid array length: threadCpuTimesMillis");
return false;
}
- return mEnv->CallBooleanMethod(mCpuTimeInStateReader, mid, tgid);
-}
-
-bool MockCpuTimeInStateReader::startAggregatingTaskCpuTimes(pid_t pid, uint16_t aggregationKey) {
- jclass cls = mEnv->GetObjectClass(mCpuTimeInStateReader);
- jmethodID mid = mEnv->GetMethodID(cls, "startAggregatingTaskCpuTimes", "(II)Z");
- if (mid == 0) {
- ALOGE("Couldn't find the method startAggregatingTaskCpuTimes");
+ if (selectedThreadCpuTimesMillis.size() != frequencyCount) {
+ ALOGE("Invalid array length: selectedThreadCpuTimesMillisArray");
return false;
}
- return mEnv->CallBooleanMethod(mCpuTimeInStateReader, mid, pid, aggregationKey);
-}
-std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
-MockCpuTimeInStateReader::getAggregatedTaskCpuFreqTimes(
- pid_t pid, const std::vector<uint16_t> &aggregationKeys) {
- jclass cls = mEnv->GetObjectClass(mCpuTimeInStateReader);
- jmethodID mid =
- mEnv->GetMethodID(cls, "getAggregatedTaskCpuFreqTimes", "(I)[Ljava/lang/String;");
- if (mid == 0) {
- ALOGE("Couldn't find the method getAggregatedTaskCpuFreqTimes");
- return {};
+ aggregateThreadCpuTimes(procPathStr, pid, threadIds, frequencyCount, selectedThreadIds.get(),
+ selectedThreadIds.size(),
+ reinterpret_cast<uint64_t *>(threadCpuTimesMillis.get()),
+ reinterpret_cast<uint64_t *>(selectedThreadCpuTimesMillis.get()));
+
+ uint64_t processCpuTime;
+ bool ret = getProcessCpuTime(procPathStr, pid, processCpuTime);
+ if (ret) {
+ estimateProcessTimeInState(processCpuTime,
+ reinterpret_cast<uint64_t *>(threadCpuTimesMillis.get()),
+ frequencyCount,
+ reinterpret_cast<uint64_t *>(processCpuTimesMillis.get()));
}
+ return ret;
+}
- std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>> map;
-
- jobjectArray stringArray =
- (jobjectArray)mEnv->CallObjectMethod(mCpuTimeInStateReader, mid, pid);
- int size = mEnv->GetArrayLength(stringArray);
- for (int i = 0; i < size; i++) {
- ScopedUtfChars line(mEnv, (jstring)mEnv->GetObjectArrayElement(stringArray, i));
- uint16_t aggregationKey;
- std::vector<std::vector<uint64_t>> times;
-
- // Each string is formatted like this: "aggKey:t0_0 t0_1...:t1_0 t1_1..."
- auto fields = android::base::Split(line.c_str(), ":");
- android::base::ParseUint(fields[0], &aggregationKey);
-
- for (int j = 1; j < fields.size(); j++) {
- auto numbers = android::base::Split(fields[j], " ");
-
- std::vector<uint64_t> chunk;
- for (int k = 0; k < numbers.size(); k++) {
- uint64_t time;
- android::base::ParseUint(numbers[k], &time);
- chunk.emplace_back(time);
- }
- times.emplace_back(chunk);
- }
-
- map.emplace(aggregationKey, times);
- }
+static const JNINativeMethod g_single_methods[] = {
+ {"readProcessCpuUsage", "(Ljava/lang/String;I[I[J[J[J)Z", (void *)readProcessCpuUsage},
+};
- return map;
+int register_com_android_internal_os_KernelSingleProcessCpuThreadReader(JNIEnv *env) {
+ return RegisterMethodsOrDie(env, "com/android/internal/os/KernelSingleProcessCpuThreadReader",
+ g_single_methods, NELEM(g_single_methods));
}
} // namespace android
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java
index 2de800bb6d00..b5720a262b3d 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java
@@ -19,87 +19,122 @@ package com.android.internal.os;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.os.FileUtils;
+
+import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.File;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class KernelSingleProcessCpuThreadReaderTest {
+ private File mProcDirectory;
+
+ @Before
+ public void setUp() {
+ Context context = InstrumentationRegistry.getContext();
+ mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ FileUtils.deleteContents(mProcDirectory);
+ }
+
@Test
public void getProcessCpuUsage() throws IOException {
- // Units are nanoseconds
- MockCpuTimeInStateReader mockReader = new MockCpuTimeInStateReader(4, new String[] {
- "0:1000000000 2000000000 3000000000:4000000000",
- "1:100000000 200000000 300000000:400000000",
- });
+ 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, 600}},
+ new int[] {4500, 500});
KernelSingleProcessCpuThreadReader reader = new KernelSingleProcessCpuThreadReader(42,
- mockReader);
- reader.setSelectedThreadIds(new int[] {2, 3});
- reader.startTrackingThreadCpuTimes();
+ mProcDirectory.toPath());
KernelSingleProcessCpuThreadReader.ProcessCpuUsage processCpuUsage =
- reader.getProcessCpuUsage();
- assertThat(mockReader.mTrackedTgid).isEqualTo(42);
- // The strings are formatted as <TID TGID AGG_KEY>, where AGG_KEY is 1 for binder
- // threads and 0 for all other threads.
- assertThat(mockReader.mTrackedTasks).containsExactly(
- "2 1",
- "3 1");
- assertThat(processCpuUsage.threadCpuTimesMillis).isEqualTo(
- new long[] {1100, 2200, 3300, 4400});
- assertThat(processCpuUsage.selectedThreadCpuTimesMillis).isEqualTo(
- new long[] {100, 200, 300, 400});
+ 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
public void getCpuFrequencyCount() throws IOException {
- MockCpuTimeInStateReader mockReader = new MockCpuTimeInStateReader(3, new String[0]);
+ setupDirectory(13,
+ new int[] {13},
+ new int[] {1000, 2000, 3000},
+ new int[][] {{100, 200, 300}},
+ new int[] {14, 15});
KernelSingleProcessCpuThreadReader reader = new KernelSingleProcessCpuThreadReader(13,
- mockReader);
+ mProcDirectory.toPath());
int cpuFrequencyCount = reader.getCpuFrequencyCount();
assertThat(cpuFrequencyCount).isEqualTo(3);
}
- public static class MockCpuTimeInStateReader implements
- KernelSingleProcessCpuThreadReader.CpuTimeInStateReader {
- private final int mCpuFrequencyCount;
- private final String[] mAggregatedTaskCpuFreqTimes;
- public int mTrackedTgid;
- public List<String> mTrackedTasks = new ArrayList<>();
-
- public MockCpuTimeInStateReader(int cpuFrequencyCount,
- String[] aggregatedTaskCpuFreqTimes) {
- mCpuFrequencyCount = cpuFrequencyCount;
- mAggregatedTaskCpuFreqTimes = aggregatedTaskCpuFreqTimes;
- }
+ private void setupDirectory(int pid, int[] threadIds, int[] cpuFrequencies,
+ int[][] threadCpuTimes, int[] processCpuTimes)
+ throws IOException {
- @Override
- public int getCpuFrequencyCount() {
- return mCpuFrequencyCount;
- }
+ assertTrue(mProcDirectory.toPath().resolve("self").toFile().mkdirs());
- @Override
- public boolean startTrackingProcessCpuTimes(int tgid) {
- mTrackedTgid = tgid;
- return true;
+ try (OutputStream timeInStateStream =
+ Files.newOutputStream(
+ mProcDirectory.toPath().resolve("self").resolve("time_in_state"))) {
+ for (int i = 0; i < cpuFrequencies.length; i++) {
+ final String line = cpuFrequencies[i] + " 0\n";
+ timeInStateStream.write(line.getBytes());
+ }
}
- public boolean startAggregatingTaskCpuTimes(int pid, int aggregationKey) {
- mTrackedTasks.add(pid + " " + aggregationKey);
- return true;
+ Path processPath = mProcDirectory.toPath().resolve(String.valueOf(pid));
+
+ // Make /proc/$PID
+ assertTrue(processPath.toFile().mkdirs());
+
+ // Write /proc/$PID/stat. Only the fields 14-17 matter.
+ try (OutputStream timeInStateStream = Files.newOutputStream(processPath.resolve("stat"))) {
+ timeInStateStream.write(
+ (pid + " (test) S 4 5 6 7 8 9 10 11 12 13 "
+ + processCpuTimes[0] + " "
+ + processCpuTimes[1] + " "
+ + "16 17 18 19 20 ...").getBytes());
}
- public String[] getAggregatedTaskCpuFreqTimes(int pid) {
- return mAggregatedTaskCpuFreqTimes;
+ // Make /proc/$PID/task
+ final Path selfThreadsPath = processPath.resolve("task");
+ assertTrue(selfThreadsPath.toFile().mkdirs());
+
+ // Make thread directories
+ for (int i = 0; i < threadIds.length; i++) {
+ // Make /proc/$PID/task/$TID
+ final Path threadPath = selfThreadsPath.resolve(String.valueOf(threadIds[i]));
+ assertTrue(threadPath.toFile().mkdirs());
+
+ // Make /proc/$PID/task/$TID/time_in_state
+ try (OutputStream timeInStateStream =
+ Files.newOutputStream(threadPath.resolve("time_in_state"))) {
+ for (int j = 0; j < cpuFrequencies.length; j++) {
+ final String line = cpuFrequencies[j] + " " + threadCpuTimes[i][j] + "\n";
+ timeInStateStream.write(line.getBytes());
+ }
+ }
}
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java
index d116d4dcbe92..121c637310c6 100644
--- a/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java
@@ -16,86 +16,146 @@
package com.android.internal.os;
-import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
+import android.content.Context;
+import android.os.FileUtils;
+
+import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.File;
import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class SystemServerCpuThreadReaderTest {
+ private File mProcDirectory;
- @Test
- public void testReadDelta() throws IOException {
- int pid = 42;
+ @Before
+ public void setUp() {
+ Context context = InstrumentationRegistry.getContext();
+ mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE);
+ }
- MockCpuTimeInStateReader mockReader = new MockCpuTimeInStateReader(4);
- // Units are nanoseconds
- mockReader.setAggregatedTaskCpuFreqTimes(new String[] {
- "0:1000000000 2000000000 3000000000:4000000000",
- "1:100000000 200000000 300000000:400000000",
- });
+ @After
+ public void tearDown() throws Exception {
+ FileUtils.deleteContents(mProcDirectory);
+ }
- SystemServerCpuThreadReader reader = new SystemServerCpuThreadReader(pid, mockReader);
+ @Test
+ public void testReaderDelta_firstTime() throws IOException {
+ int pid = 42;
+ setupDirectory(
+ pid,
+ new int[] {42, 1, 2, 3},
+ new int[] {1000, 2000},
+ // Units are 10ms aka 10000Us
+ new int[][] {{100, 200}, {0, 200}, {0, 300}, {0, 400}},
+ new int[] {1400, 1500});
+
+ SystemServerCpuThreadReader reader = new SystemServerCpuThreadReader(
+ mProcDirectory.toPath(), pid);
reader.setBinderThreadNativeTids(new int[] {1, 3});
-
- // The first invocation of readDelta populates the "last" snapshot
SystemServerCpuThreadReader.SystemServiceCpuThreadTimes systemServiceCpuThreadTimes =
reader.readDelta();
+ assertArrayEquals(new long[] {100 * 10000, 1100 * 10000},
+ systemServiceCpuThreadTimes.threadCpuTimesUs);
+ assertArrayEquals(new long[] {0, 600 * 10000},
+ systemServiceCpuThreadTimes.binderThreadCpuTimesUs);
+ }
- assertThat(systemServiceCpuThreadTimes.threadCpuTimesUs)
- .isEqualTo(new long[] {1100000, 2200000, 3300000, 4400000});
- assertThat(systemServiceCpuThreadTimes.binderThreadCpuTimesUs)
- .isEqualTo(new long[] {100000, 200000, 300000, 400000});
-
- mockReader.setAggregatedTaskCpuFreqTimes(new String[] {
- "0:1010000000 2020000000 3030000000:4040000000",
- "1:101000000 202000000 303000000:404000000",
- });
+ @Test
+ public void testReaderDelta_nextTime() throws IOException {
+ int pid = 42;
+ setupDirectory(
+ pid,
+ new int[] {42, 1, 2, 3},
+ new int[] {1000, 2000},
+ new int[][] {{100, 200}, {0, 200}, {0, 300}, {0, 400}},
+ new int[] {1400, 1500});
+
+ SystemServerCpuThreadReader reader = new SystemServerCpuThreadReader(
+ mProcDirectory.toPath(), pid);
+ reader.setBinderThreadNativeTids(new int[] {1, 3});
- // The second invocation gets the actual delta
- systemServiceCpuThreadTimes = reader.readDelta();
+ // First time, populate "last" snapshot
+ reader.readDelta();
- assertThat(systemServiceCpuThreadTimes.threadCpuTimesUs)
- .isEqualTo(new long[] {11000, 22000, 33000, 44000});
- assertThat(systemServiceCpuThreadTimes.binderThreadCpuTimesUs)
- .isEqualTo(new long[] {1000, 2000, 3000, 4000});
- }
+ FileUtils.deleteContents(mProcDirectory);
+ setupDirectory(
+ pid,
+ new int[] {42, 1, 2, 3},
+ new int[] {1000, 2000},
+ new int[][] {{500, 600}, {700, 800}, {900, 1000}, {1100, 1200}},
+ new int[] {2400, 2500});
- public static class MockCpuTimeInStateReader implements
- KernelSingleProcessCpuThreadReader.CpuTimeInStateReader {
- private final int mCpuFrequencyCount;
- private String[] mAggregatedTaskCpuFreqTimes;
+ // Second time, get the actual delta
+ SystemServerCpuThreadReader.SystemServiceCpuThreadTimes systemServiceCpuThreadTimes =
+ reader.readDelta();
- MockCpuTimeInStateReader(int frequencyCount) {
- mCpuFrequencyCount = frequencyCount;
- }
+ assertArrayEquals(new long[] {3100 * 10000, 2500 * 10000},
+ systemServiceCpuThreadTimes.threadCpuTimesUs);
+ assertArrayEquals(new long[] {1800 * 10000, 1400 * 10000},
+ systemServiceCpuThreadTimes.binderThreadCpuTimesUs);
+ }
- @Override
- public int getCpuFrequencyCount() {
- return mCpuFrequencyCount;
- }
+ private void setupDirectory(int pid, int[] threadIds, int[] cpuFrequencies, int[][] cpuTimes,
+ int[] processCpuTimes)
+ throws IOException {
- @Override
- public boolean startTrackingProcessCpuTimes(int tgid) {
- return true;
- }
+ assertTrue(mProcDirectory.toPath().resolve("self").toFile().mkdirs());
- public boolean startAggregatingTaskCpuTimes(int pid, int aggregationKey) {
- return true;
+ try (OutputStream timeInStateStream =
+ Files.newOutputStream(
+ mProcDirectory.toPath().resolve("self").resolve("time_in_state"))) {
+ for (int i = 0; i < cpuFrequencies.length; i++) {
+ final String line = cpuFrequencies[i] + " 0\n";
+ timeInStateStream.write(line.getBytes());
+ }
}
- public void setAggregatedTaskCpuFreqTimes(String[] mAggregatedTaskCpuFreqTimes) {
- this.mAggregatedTaskCpuFreqTimes = mAggregatedTaskCpuFreqTimes;
+ Path processPath = mProcDirectory.toPath().resolve(String.valueOf(pid));
+ // Make /proc/$PID
+ assertTrue(processPath.toFile().mkdirs());
+
+ // Write /proc/$PID/stat. Only the fields 14-17 matter.
+ try (OutputStream timeInStateStream = Files.newOutputStream(processPath.resolve("stat"))) {
+ timeInStateStream.write(
+ (pid + " (test) S 4 5 6 7 8 9 10 11 12 13 "
+ + processCpuTimes[0] + " "
+ + processCpuTimes[1] + " "
+ + "16 17 18 19 20 ...").getBytes());
}
- public String[] getAggregatedTaskCpuFreqTimes(int pid) {
- return mAggregatedTaskCpuFreqTimes;
+ // Make /proc/$PID/task
+ final Path selfThreadsPath = processPath.resolve("task");
+ assertTrue(selfThreadsPath.toFile().mkdirs());
+
+ // Make thread directories
+ for (int i = 0; i < threadIds.length; i++) {
+ // Make /proc/$PID/task/$TID
+ final Path threadPath = selfThreadsPath.resolve(String.valueOf(threadIds[i]));
+ assertTrue(threadPath.toFile().mkdirs());
+
+ // Make /proc/$PID/task/$TID/time_in_state
+ try (OutputStream timeInStateStream =
+ Files.newOutputStream(threadPath.resolve("time_in_state"))) {
+ for (int j = 0; j < cpuFrequencies.length; j++) {
+ final String line = cpuFrequencies[j] + " " + cpuTimes[i][j] + "\n";
+ timeInStateStream.write(line.getBytes());
+ }
+ }
}
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
index c8e858553e7d..dbb36fbd1650 100644
--- a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
@@ -66,6 +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(
+ 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});
@@ -145,7 +146,9 @@ public class SystemServicePowerCalculatorTest {
super(null);
}
- public void setCpuTimes(long[] threadCpuTimesUs, long[] binderThreadCpuTimesUs) {
+ public void setCpuTimes(long[] processCpuTimesUs, long[] threadCpuTimesUs,
+ long[] binderThreadCpuTimesUs) {
+ mThreadTimes.processCpuTimesUs = processCpuTimesUs;
mThreadTimes.threadCpuTimesUs = threadCpuTimesUs;
mThreadTimes.binderThreadCpuTimesUs = binderThreadCpuTimesUs;
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 82e9b468fea4..083e970cc434 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -267,8 +267,6 @@ public final class BatteryStatsService extends IBatteryStats.Stub
mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
com.android.internal.R.integer.config_radioScanningTimeout) * 1000L);
mStats.setPowerProfileLocked(new PowerProfile(context));
- mStats.startTrackingSystemServerCpuTime();
-
mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mStats);
}