diff options
| author | 2021-01-16 00:10:30 +0000 | |
|---|---|---|
| committer | 2021-01-28 07:16:16 +0000 | |
| commit | 8d247edc93fe0fe75032c361a6e2cf7c8bccb5a7 (patch) | |
| tree | a12c7b66c4a501d8b8dc61376f2f0692968b4934 | |
| parent | 258ae119c939fba262d17eb00aaf3aa05e87060b (diff) | |
Revert "Revert "Use eBPF-based time-in-state monitoring for groups of threads""
This reverts commit c9e9058347e4f99f7e580869055aae039e05d8ee.
This is the original CL, unchanged. The test that was the reason for
the rollback now passes, see http://go/forrest-run/L29200000799760670
The reason for the test failure was that Cuttlefish does not
define any CPU frequencies, which caused https://android.googlesource.com/platform/frameworks/native/+/refs/changes/65/1558465/2/libs/cputimeinstate/cputimeinstate.cpp#578
to fall into a near-infinite loop, thus locking up BatteryStats.
The no-frequencies issue was addressed in https://r.android.com/1558465.
Reason for revert: rolling forward since the test failure has been addressed
Bug: 169279846
Change-Id: I537561b4c86ef2aaf236b1807b844855fef38f8b
(cherry picked from commit 4a7792b30cf2407a0ecb69963e981a7274e1f962)
Merged-In: I537561b4c86ef2aaf236b1807b844855fef38f8b
8 files changed, 479 insertions, 678 deletions
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index d475c658a3a0..2b034b0667d6 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -1099,16 +1099,6 @@ 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; @@ -10856,6 +10846,14 @@ 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; } @@ -11508,8 +11506,6 @@ public class BatteryStatsImpl extends BatteryStats { MeasuredEnergyStats.resetIfNotNull(mGlobalMeasuredEnergyStats); - resetIfNotNull(mSystemServerCpuTimesUs, false, elapsedRealtimeUs); - resetIfNotNull(mSystemServerThreadCpuTimesUs, false, elapsedRealtimeUs); resetIfNotNull(mBinderThreadCpuTimesUs, false, elapsedRealtimeUs); mLastHistoryStepDetails = null; @@ -12695,27 +12691,17 @@ public class BatteryStatsImpl extends BatteryStats { return; } - if (mSystemServerCpuTimesUs == null) { - mSystemServerCpuTimesUs = new LongSamplingCounterArray(mOnBatteryTimeBase); - mSystemServerThreadCpuTimesUs = new LongSamplingCounterArray(mOnBatteryTimeBase); + if (mBinderThreadCpuTimesUs == null) { 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 (binder threads/total threads/%)"); - long totalCpuTimeMs = 0; - long totalThreadTimeMs = 0; + Slog.d(TAG, "System server threads per CPU cluster (incoming binder threads)"); long binderThreadTimeMs = 0; int cpuIndex = 0; - final long[] systemServerCpuTimesUs = - mSystemServerCpuTimesUs.getCountsLocked(0); - final long[] systemServerThreadCpuTimesUs = - mSystemServerThreadCpuTimesUs.getCountsLocked(0); - final long[] binderThreadCpuTimesUs = - mBinderThreadCpuTimesUs.getCountsLocked(0); + final long[] binderThreadCpuTimesUs = mBinderThreadCpuTimesUs.getCountsLocked( + BatteryStats.STATS_SINCE_CHARGED); int index = 0; int numCpuClusters = mPowerProfile.getNumCpuClusters(); for (int cluster = 0; cluster < numCpuClusters; cluster++) { @@ -12726,28 +12712,15 @@ public class BatteryStatsImpl extends BatteryStats { if (speed != 0) { sb.append(", "); } - long totalCountMs = systemServerThreadCpuTimesUs[index] / 1000; long binderCountMs = binderThreadCpuTimesUs[index] / 1000; - sb.append(String.format("%d/%d(%.1f%%)", - binderCountMs, - totalCountMs, - totalCountMs != 0 ? (double) binderCountMs * 100 / totalCountMs : 0)); + sb.append(TextUtils.formatSimple("%10d", binderCountMs)); - 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)); } } @@ -14022,60 +13995,16 @@ public class BatteryStatsImpl extends BatteryStats { } + /** + * Estimates the time spent by the system server handling incoming binder requests. + */ @Override public long[] getSystemServiceTimeAtCpuSpeeds() { - // 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) { + if (mBinderThreadCpuTimesUs == null) { return null; } - 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; + return mBinderThreadCpuTimesUs.getCountsLocked(BatteryStats.STATS_SINCE_CHARGED); } /** @@ -14506,7 +14435,7 @@ public class BatteryStatsImpl extends BatteryStats { } updateSystemServiceCallStats(); - if (mSystemServerThreadCpuTimesUs != null) { + if (mBinderThreadCpuTimesUs != null) { pw.println("Per UID System server binder time in ms:"); long[] systemServiceTimeAtCpuSpeeds = getSystemServiceTimeAtCpuSpeeds(); for (int i = 0; i < size; i++) { @@ -16097,9 +16026,6 @@ public class BatteryStatsImpl extends BatteryStats { mUidStats.append(uid, u); } - mSystemServerCpuTimesUs = LongSamplingCounterArray.readFromParcel(in, mOnBatteryTimeBase); - mSystemServerThreadCpuTimesUs = LongSamplingCounterArray.readFromParcel(in, - mOnBatteryTimeBase); mBinderThreadCpuTimesUs = LongSamplingCounterArray.readFromParcel(in, mOnBatteryTimeBase); } @@ -16308,8 +16234,6 @@ 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 e6a962312a00..4d2a08a4bcf3 100644 --- a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java +++ b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java @@ -16,23 +16,12 @@ 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; /** @@ -45,93 +34,65 @@ 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; - /** Where the proc filesystem is mounted */ - private final Path mProcPath; + private final CpuTimeInStateReader mCpuTimeInStateReader; - // How long a CPU jiffy is in milliseconds. - private final long mJiffyMillis; + private int[] mSelectedThreadNativeTids = new int[0]; // Sorted - // Path: /proc/<pid>/stat - private final String mProcessStatFilePath; + /** + * Count of frequencies read from the {@code time_in_state} file. + */ + private int mFrequencyCount; - // Path: /proc/<pid>/task - private final Path mThreadsDirectoryPath; + private boolean mIsTracking; /** - * Count of frequencies read from the {@code time_in_state} file. Read from {@link - * #mProcTimeInStateReader#getCpuFrequenciesKhz()}. + * A CPU time-in-state provider for testing. Imitates the behavior of the corresponding + * methods in frameworks/native/libs/cputimeinstate/cputimeinstate.c */ - private int mFrequencyCount; + @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); + } /** * 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, - Path procPath) throws IOException { + public KernelSingleProcessCpuThreadReader(int pid, + @Nullable CpuTimeInStateReader cpuTimeInStateReader) throws IOException { mPid = pid; - 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); + mCpuTimeInStateReader = cpuTimeInStateReader; } /** @@ -142,7 +103,7 @@ public class KernelSingleProcessCpuThreadReader { @Nullable public static KernelSingleProcessCpuThreadReader create(int pid) { try { - return new KernelSingleProcessCpuThreadReader(pid, DEFAULT_PROC_PATH); + return new KernelSingleProcessCpuThreadReader(pid, null); } catch (IOException e) { Slog.e(TAG, "Failed to initialize KernelSingleProcessCpuThreadReader", e); return null; @@ -150,146 +111,98 @@ public class KernelSingleProcessCpuThreadReader { } /** - * Get the CPU frequencies that correspond to the times reported in {@link - * ProcessCpuUsage#processCpuTimesMillis} etc. + * 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}. */ public int getCpuFrequencyCount() { if (mFrequencyCount == 0) { - mFrequencyCount = mProcTimeInStateReader.getFrequenciesKhz().length; + mFrequencyCount = getCpuFrequencyCount(mCpuTimeInStateReader); } return mFrequencyCount; } /** - * 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. + * 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. */ @Nullable - public ProcessCpuUsage getProcessCpuUsage(int[] selectedThreadIds) { + public ProcessCpuUsage getProcessCpuUsage() { if (DEBUG) { - 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; + Slog.d(TAG, "Reading CPU thread usages for PID " + 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); - return null; - } + ProcessCpuUsage processCpuUsage = new ProcessCpuUsage(getCpuFrequencyCount()); - 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 + boolean result = readProcessCpuUsage(mPid, + processCpuUsage.threadCpuTimesMillis, + processCpuUsage.selectedThreadCpuTimesMillis, + mCpuTimeInStateReader); + if (!result) { 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; + if (DEBUG) { + Slog.d(TAG, "threadCpuTimesMillis = " + + Arrays.toString(processCpuUsage.threadCpuTimesMillis)); + Slog.d(TAG, "selectedThreadCpuTimesMillis = " + + Arrays.toString(processCpuUsage.selectedThreadCpuTimesMillis)); } 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 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 int getCpuFrequencyCount(CpuTimeInStateReader reader); + + private native boolean startTrackingProcessCpuTimes(int pid, CpuTimeInStateReader reader); + + private native boolean startAggregatingThreadCpuTimes(int[] selectedThreadIds, + CpuTimeInStateReader reader); - private native boolean readProcessCpuUsage(String procPath, int pid, int[] selectedThreadIds, - long[] processCpuTimesMillis, long[] threadCpuTimesMillis, - long[] selectedThreadCpuTimesMillis); + private native boolean readProcessCpuUsage(int pid, + long[] threadCpuTimesMillis, + long[] selectedThreadCpuTimesMillis, + CpuTimeInStateReader reader); } diff --git a/core/java/com/android/internal/os/SystemServerCpuThreadReader.java b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java index fbbee94feacc..fbad75e93a17 100644 --- a/core/java/com/android/internal/os/SystemServerCpuThreadReader.java +++ b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java @@ -22,8 +22,6 @@ 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 @@ -31,9 +29,7 @@ import java.util.Arrays; */ public class SystemServerCpuThreadReader { private final KernelSingleProcessCpuThreadReader mKernelCpuThreadReader; - private int[] mBinderThreadNativeTids = new int[0]; // Sorted - private long[] mLastProcessCpuTimeUs; private long[] mLastThreadCpuTimesUs; private long[] mLastBinderThreadCpuTimesUs; @@ -41,8 +37,6 @@ 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 @@ -61,8 +55,10 @@ public class SystemServerCpuThreadReader { } @VisibleForTesting - public SystemServerCpuThreadReader(Path procPath, int pid) throws IOException { - this(new KernelSingleProcessCpuThreadReader(pid, procPath)); + public SystemServerCpuThreadReader(int pid, + KernelSingleProcessCpuThreadReader.CpuTimeInStateReader cpuTimeInStateReader) + throws IOException { + this(new KernelSingleProcessCpuThreadReader(pid, cpuTimeInStateReader)); } @VisibleForTesting @@ -70,9 +66,15 @@ 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) { - mBinderThreadNativeTids = nativeTids.clone(); - Arrays.sort(mBinderThreadNativeTids); + mKernelCpuThreadReader.setSelectedThreadIds(nativeTids); } /** @@ -81,33 +83,27 @@ public class SystemServerCpuThreadReader { @Nullable public SystemServiceCpuThreadTimes readDelta() { final int numCpuFrequencies = mKernelCpuThreadReader.getCpuFrequencyCount(); - if (mLastProcessCpuTimeUs == null) { - mLastProcessCpuTimeUs = new long[numCpuFrequencies]; + if (mLastThreadCpuTimesUs == null) { 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(mBinderThreadNativeTids); + mKernelCpuThreadReader.getProcessCpuUsage(); 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 52bed6bcfce3..dfae68429f6d 100644 --- a/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp +++ b/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp @@ -26,244 +26,308 @@ #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)); -// 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); +// 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> &); +}; - 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 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; } - outThreadIds.reserve(threadCount); + bool startTrackingProcessCpuTimes(pid_t tgid) { + return android::bpf::startTrackingProcessCpuTimes(tgid); + } - 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]); + bool startAggregatingTaskCpuTimes(pid_t pid, uint16_t aggregationKey) { + return android::bpf::startAggregatingTaskCpuTimes(pid, aggregationKey); } - free(dirlist); - return true; -} + 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); + } +}; -// 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; +// 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); + + 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(); } +} - auto lines = android::base::Split(data, "\n"); - size_t index = 0; - for (const auto &line : lines) { - if (line.empty()) { - continue; - } +static jint getCpuFrequencyCount(JNIEnv *env, jclass, jobject cpuTimeInStateReaderObject) { + std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader( + getCpuTimeInStateReader(env, cpuTimeInStateReaderObject)); + return cpuTimeInStateReader->getCpuFrequencyCount(); +} - 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()); +static jboolean startTrackingProcessCpuTimes(JNIEnv *env, jclass, jint tgid, + jobject cpuTimeInStateReaderObject) { + std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader( + getCpuTimeInStateReader(env, cpuTimeInStateReaderObject)); + return cpuTimeInStateReader->startTrackingProcessCpuTimes(tgid); +} + +static jboolean startAggregatingThreadCpuTimes(JNIEnv *env, jclass, jintArray selectedThreadIdArray, + jobject cpuTimeInStateReaderObject) { + ScopedIntArrayRO selectedThreadIds(env, selectedThreadIdArray); + std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader( + getCpuTimeInStateReader(env, cpuTimeInStateReaderObject)); + + for (int i = 0; i < selectedThreadIds.size(); i++) { + if (!cpuTimeInStateReader->startAggregatingTaskCpuTimes(selectedThreadIds[i], + SELECTED_THREAD_AGGREGATION_KEY)) { return false; } - if (index < frequencyCount) { - outThreadTimeInState[index] = timeInState; - } - index++; } + 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) { - ALOGE("Incorrect number of frequencies %u in %s. Expected %u", - (uint32_t)outThreadTimeInState.size(), timeInStateFilePath.c_str(), - (uint32_t)frequencyCount); + ALOGE("CPU time-in-state reader returned data for %zu frequencies; expected: %zu", index, + frequencyCount); return false; } return true; } -static int pidCompare(const void *a, const void *b) { - return (*(pid_t *)a - *(pid_t *)b); -} - -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; -} - -// Reads all /proc/<pid>/task/*/time_in_state files and aggregates per-frequency +// 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 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; - } +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)); - 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; - } + const size_t frequencyCount = cpuTimeInStateReader->getCpuFrequencyCount(); - bool selectedThread = isSelectedThread(tid, selectedThreadIds, selectedThreadCount); - for (size_t j = 0; j < frequencyCount; j++) { - threadCpuTimesMillis[j] += timeInState[j]; - if (selectedThread) { - selectedThreadCpuTimesMillis[j] += timeInState[j]; - } - } + if (threadCpuTimesMillis.size() != frequencyCount) { + ALOGE("Invalid threadCpuTimesMillis array length: %zu frequencies; expected: %zu", + threadCpuTimesMillis.size(), frequencyCount); + return false; } + + if (selectedThreadCpuTimesMillis.size() != frequencyCount) { + ALOGE("Invalid selectedThreadCpuTimesMillis array length: %zu frequencies; expected: %zu", + selectedThreadCpuTimesMillis.size(), frequencyCount); + return false; + } + for (size_t i = 0; i < frequencyCount; i++) { - threadCpuTimesMillis[i] *= gJiffyMillis; - selectedThreadCpuTimesMillis[i] *= gJiffyMillis; + threadCpuTimesMillis[i] = 0; + selectedThreadCpuTimesMillis[i] = 0; } -} -// 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)) { + 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); return false; } - auto fields = android::base::Split(data, " "); - uint64_t utime, stime; - - // 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()); + if (!flattenTimeInStateData(threadCpuTimesMillis, (*data)[DEFAULT_THREAD_AGGREGATION_KEY])) { return false; } - outTimeMillis = (utime + stime) * gJiffyMillis; - return true; -} + if (!flattenTimeInStateData(selectedThreadCpuTimesMillis, + (*data)[SELECTED_THREAD_AGGREGATION_KEY])) { + return false; + } -// 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; + // threadCpuTimesMillis returns CPU times for _all_ threads, including the selected ones for (size_t i = 0; i < frequencyCount; i++) { - totalCpuTimeAllThreads += threadCpuTimesMillis[i]; + threadCpuTimesMillis[i] += selectedThreadCpuTimesMillis[i]; } - 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; - } - } + return true; } -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); +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}, +}; - std::string procPathStr(procPathChars.c_str()); +int register_com_android_internal_os_KernelSingleProcessCpuThreadReader(JNIEnv *env) { + return RegisterMethodsOrDie(env, "com/android/internal/os/KernelSingleProcessCpuThreadReader", + g_single_methods, NELEM(g_single_methods)); +} - // 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()); +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"); return false; } + return (size_t)mEnv->CallIntMethod(mCpuTimeInStateReader, mid); +} - size_t frequencyCount = processCpuTimesMillis.size(); - - if (threadCpuTimesMillis.size() != frequencyCount) { - ALOGE("Invalid array length: threadCpuTimesMillis"); +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"); return false; } - if (selectedThreadCpuTimesMillis.size() != frequencyCount) { - ALOGE("Invalid array length: selectedThreadCpuTimesMillisArray"); + 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"); return false; } + return mEnv->CallBooleanMethod(mCpuTimeInStateReader, mid, pid, aggregationKey); +} - 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())); +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 {}; } - return ret; -} -static const JNINativeMethod g_single_methods[] = { - {"readProcessCpuUsage", "(Ljava/lang/String;I[I[J[J[J)Z", (void *)readProcessCpuUsage}, -}; + std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>> 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)); + 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); + } + + return map; } } // 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 b5720a262b3d..2de800bb6d00 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java @@ -19,122 +19,87 @@ 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.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; @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 { - 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}); + // Units are nanoseconds + MockCpuTimeInStateReader mockReader = new MockCpuTimeInStateReader(4, new String[] { + "0:1000000000 2000000000 3000000000:4000000000", + "1:100000000 200000000 300000000:400000000", + }); KernelSingleProcessCpuThreadReader reader = new KernelSingleProcessCpuThreadReader(42, - mProcDirectory.toPath()); + mockReader); + reader.setSelectedThreadIds(new int[] {2, 3}); + reader.startTrackingThreadCpuTimes(); KernelSingleProcessCpuThreadReader.ProcessCpuUsage processCpuUsage = - 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}); + 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}); } @Test public void getCpuFrequencyCount() throws IOException { - setupDirectory(13, - new int[] {13}, - new int[] {1000, 2000, 3000}, - new int[][] {{100, 200, 300}}, - new int[] {14, 15}); + MockCpuTimeInStateReader mockReader = new MockCpuTimeInStateReader(3, new String[0]); KernelSingleProcessCpuThreadReader reader = new KernelSingleProcessCpuThreadReader(13, - mProcDirectory.toPath()); + mockReader); int cpuFrequencyCount = reader.getCpuFrequencyCount(); assertThat(cpuFrequencyCount).isEqualTo(3); } - private void setupDirectory(int pid, int[] threadIds, int[] cpuFrequencies, - int[][] threadCpuTimes, int[] processCpuTimes) - throws IOException { - - assertTrue(mProcDirectory.toPath().resolve("self").toFile().mkdirs()); - - 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 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; } - Path processPath = mProcDirectory.toPath().resolve(String.valueOf(pid)); + @Override + public int getCpuFrequencyCount() { + return mCpuFrequencyCount; + } - // Make /proc/$PID - assertTrue(processPath.toFile().mkdirs()); + @Override + public boolean startTrackingProcessCpuTimes(int tgid) { + mTrackedTgid = tgid; + return true; + } - // 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 boolean startAggregatingTaskCpuTimes(int pid, int aggregationKey) { + mTrackedTasks.add(pid + " " + aggregationKey); + return true; } - // 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()); - } - } + public String[] getAggregatedTaskCpuFreqTimes(int pid) { + return mAggregatedTaskCpuFreqTimes; } } } 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 121c637310c6..d116d4dcbe92 100644 --- a/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java @@ -16,146 +16,86 @@ package com.android.internal.os; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; -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; - - @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 testReaderDelta_firstTime() throws IOException { + public void testReadDelta() 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); + + MockCpuTimeInStateReader mockReader = new MockCpuTimeInStateReader(4); + // Units are nanoseconds + mockReader.setAggregatedTaskCpuFreqTimes(new String[] { + "0:1000000000 2000000000 3000000000:4000000000", + "1:100000000 200000000 300000000:400000000", + }); + + SystemServerCpuThreadReader reader = new SystemServerCpuThreadReader(pid, mockReader); 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); - } - - @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}); - // First time, populate "last" snapshot - reader.readDelta(); + assertThat(systemServiceCpuThreadTimes.threadCpuTimesUs) + .isEqualTo(new long[] {1100000, 2200000, 3300000, 4400000}); + assertThat(systemServiceCpuThreadTimes.binderThreadCpuTimesUs) + .isEqualTo(new long[] {100000, 200000, 300000, 400000}); - 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}); + mockReader.setAggregatedTaskCpuFreqTimes(new String[] { + "0:1010000000 2020000000 3030000000:4040000000", + "1:101000000 202000000 303000000:404000000", + }); - // Second time, get the actual delta - SystemServerCpuThreadReader.SystemServiceCpuThreadTimes systemServiceCpuThreadTimes = - reader.readDelta(); + // The second invocation gets the actual delta + systemServiceCpuThreadTimes = reader.readDelta(); - assertArrayEquals(new long[] {3100 * 10000, 2500 * 10000}, - systemServiceCpuThreadTimes.threadCpuTimesUs); - assertArrayEquals(new long[] {1800 * 10000, 1400 * 10000}, - systemServiceCpuThreadTimes.binderThreadCpuTimesUs); + assertThat(systemServiceCpuThreadTimes.threadCpuTimesUs) + .isEqualTo(new long[] {11000, 22000, 33000, 44000}); + assertThat(systemServiceCpuThreadTimes.binderThreadCpuTimesUs) + .isEqualTo(new long[] {1000, 2000, 3000, 4000}); } - private void setupDirectory(int pid, int[] threadIds, int[] cpuFrequencies, int[][] cpuTimes, - int[] processCpuTimes) - throws IOException { + public static class MockCpuTimeInStateReader implements + KernelSingleProcessCpuThreadReader.CpuTimeInStateReader { + private final int mCpuFrequencyCount; + private String[] mAggregatedTaskCpuFreqTimes; - assertTrue(mProcDirectory.toPath().resolve("self").toFile().mkdirs()); + MockCpuTimeInStateReader(int frequencyCount) { + mCpuFrequencyCount = frequencyCount; + } + + @Override + public int getCpuFrequencyCount() { + return mCpuFrequencyCount; + } + + @Override + public boolean startTrackingProcessCpuTimes(int 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) { + 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 void setAggregatedTaskCpuFreqTimes(String[] mAggregatedTaskCpuFreqTimes) { + this.mAggregatedTaskCpuFreqTimes = 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()); - } - } + public String[] getAggregatedTaskCpuFreqTimes(int pid) { + return mAggregatedTaskCpuFreqTimes; } } } 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 a5cafb974b0e..24741fe110ce 100644 --- a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java @@ -62,7 +62,6 @@ 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}); @@ -144,9 +143,7 @@ public class SystemServicePowerCalculatorTest { super(null); } - public void setCpuTimes(long[] processCpuTimesUs, long[] threadCpuTimesUs, - long[] binderThreadCpuTimesUs) { - mThreadTimes.processCpuTimesUs = processCpuTimesUs; + public void setCpuTimes(long[] threadCpuTimesUs, long[] binderThreadCpuTimesUs) { 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 50e55579dd74..c2872405ae18 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -254,6 +254,8 @@ 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); } |