Loading core/java/com/android/internal/os/BatteryStatsImpl.java +19 −97 Original line number Diff line number Diff line Loading @@ -106,7 +106,6 @@ import com.android.internal.power.MeasuredEnergyArray; import com.android.internal.power.MeasuredEnergyStats; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.XmlUtils; Loading @@ -122,7 +121,6 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; Loading Loading @@ -1079,16 +1077,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. */ Loading Loading @@ -10756,6 +10744,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; } Loading Loading @@ -11411,8 +11407,6 @@ public class BatteryStatsImpl extends BatteryStats { mExternalSync.scheduleSync("reset", ExternalStatsSync.UPDATE_ENERGY); } resetIfNotNull(mSystemServerCpuTimesUs, false, elapsedRealtimeUs); resetIfNotNull(mSystemServerThreadCpuTimesUs, false, elapsedRealtimeUs); resetIfNotNull(mBinderThreadCpuTimesUs, false, elapsedRealtimeUs); mLastHistoryStepDetails = null; Loading Loading @@ -12511,27 +12505,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++) { Loading @@ -12542,28 +12526,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)); } } Loading Loading @@ -13853,60 +13824,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); } /** Loading Loading @@ -14306,7 +14233,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++) { Loading Loading @@ -15872,9 +15799,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); } Loading Loading @@ -16083,8 +16007,6 @@ public class BatteryStatsImpl extends BatteryStats { } else { out.writeInt(0); } LongSamplingCounterArray.writeToParcel(out, mSystemServerCpuTimesUs); LongSamplingCounterArray.writeToParcel(out, mSystemServerThreadCpuTimesUs); LongSamplingCounterArray.writeToParcel(out, mBinderThreadCpuTimesUs); } core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java +103 −190 Original line number Diff line number Diff line Loading @@ -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; /** Loading @@ -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; private final int mPid; private final CpuTimeInStateReader mCpuTimeInStateReader; private int[] mSelectedThreadNativeTids = new int[0]; // Sorted /** * The name of the file to read CPU statistics from, must be found in {@code * /proc/$PID/task/$TID} * Count of frequencies read from the {@code time_in_state} file. */ 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 int mFrequencyCount; /** Where the proc filesystem is mounted */ private final Path mProcPath; private boolean mIsTracking; // How long a CPU jiffy is in milliseconds. private final long mJiffyMillis; /** * A CPU time-in-state provider for testing. Imitates the behavior of the corresponding * methods in frameworks/native/libs/cputimeinstate/cputimeinstate.c */ @VisibleForTesting public interface CpuTimeInStateReader { /** * Returns the overall number of cluster-frequency combinations. */ int getCpuFrequencyCount(); // Path: /proc/<pid>/stat private final String mProcessStatFilePath; /** * Returns true to indicate success. * * Called from native. */ boolean startTrackingProcessCpuTimes(int tgid); // Path: /proc/<pid>/task private final Path mThreadsDirectoryPath; /** * Returns true to indicate success. * * Called from native. */ boolean startAggregatingTaskCpuTimes(int pid, int aggregationKey); /** * Count of frequencies read from the {@code time_in_state} file. Read from {@link * #mProcTimeInStateReader#getCpuFrequenciesKhz()}. * 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. */ private int mFrequencyCount; 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; } /** Loading @@ -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; Loading @@ -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 int getCpuFrequencyCount() { if (mFrequencyCount == 0) { mFrequencyCount = mProcTimeInStateReader.getFrequenciesKhz().length; 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; } 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 * @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. */ @Nullable public ProcessCpuUsage getProcessCpuUsage(int[] selectedThreadIds) { if (DEBUG) { Slog.d(TAG, "Reading CPU thread usages with directory " + mProcPath + " process ID " + mPid); public void setSelectedThreadIds(int[] nativeTids) { mSelectedThreadNativeTids = nativeTids.clone(); if (mIsTracking) { startAggregatingThreadCpuTimes(mSelectedThreadNativeTids, mCpuTimeInStateReader); } 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; } if (!isSorted(selectedThreadIds)) { throw new IllegalArgumentException("selectedThreadIds is not sorted: " + Arrays.toString(selectedThreadIds)); /** * Get the CPU frequencies that correspond to the times reported in {@link ProcessCpuUsage}. */ public int getCpuFrequencyCount() { if (mFrequencyCount == 0) { mFrequencyCount = getCpuFrequencyCount(mCpuTimeInStateReader); } if (!Process.readProcFile(mProcessStatFilePath, PROCESS_FULL_STATS_FORMAT, null, mProcessFullStatsData, null)) { Slog.e(TAG, "Failed to read process stat file " + mProcessStatFilePath); return null; return mFrequencyCount; } long utime = mProcessFullStatsData[PROCESS_FULL_STAT_UTIME]; long stime = mProcessFullStatsData[PROCESS_FULL_STAT_STIME]; /** * 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() { if (DEBUG) { Slog.d(TAG, "Reading CPU thread usages for PID " + mPid); } long processCpuTimeMillis = (utime + stime) * mJiffyMillis; ProcessCpuUsage processCpuUsage = new ProcessCpuUsage(getCpuFrequencyCount()); 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); } Loading
core/java/com/android/internal/os/BatteryStatsImpl.java +19 −97 Original line number Diff line number Diff line Loading @@ -106,7 +106,6 @@ import com.android.internal.power.MeasuredEnergyArray; import com.android.internal.power.MeasuredEnergyStats; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.XmlUtils; Loading @@ -122,7 +121,6 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; Loading Loading @@ -1079,16 +1077,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. */ Loading Loading @@ -10756,6 +10744,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; } Loading Loading @@ -11411,8 +11407,6 @@ public class BatteryStatsImpl extends BatteryStats { mExternalSync.scheduleSync("reset", ExternalStatsSync.UPDATE_ENERGY); } resetIfNotNull(mSystemServerCpuTimesUs, false, elapsedRealtimeUs); resetIfNotNull(mSystemServerThreadCpuTimesUs, false, elapsedRealtimeUs); resetIfNotNull(mBinderThreadCpuTimesUs, false, elapsedRealtimeUs); mLastHistoryStepDetails = null; Loading Loading @@ -12511,27 +12505,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++) { Loading @@ -12542,28 +12526,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)); } } Loading Loading @@ -13853,60 +13824,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); } /** Loading Loading @@ -14306,7 +14233,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++) { Loading Loading @@ -15872,9 +15799,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); } Loading Loading @@ -16083,8 +16007,6 @@ public class BatteryStatsImpl extends BatteryStats { } else { out.writeInt(0); } LongSamplingCounterArray.writeToParcel(out, mSystemServerCpuTimesUs); LongSamplingCounterArray.writeToParcel(out, mSystemServerThreadCpuTimesUs); LongSamplingCounterArray.writeToParcel(out, mBinderThreadCpuTimesUs); }
core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java +103 −190 Original line number Diff line number Diff line Loading @@ -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; /** Loading @@ -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; private final int mPid; private final CpuTimeInStateReader mCpuTimeInStateReader; private int[] mSelectedThreadNativeTids = new int[0]; // Sorted /** * The name of the file to read CPU statistics from, must be found in {@code * /proc/$PID/task/$TID} * Count of frequencies read from the {@code time_in_state} file. */ 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 int mFrequencyCount; /** Where the proc filesystem is mounted */ private final Path mProcPath; private boolean mIsTracking; // How long a CPU jiffy is in milliseconds. private final long mJiffyMillis; /** * A CPU time-in-state provider for testing. Imitates the behavior of the corresponding * methods in frameworks/native/libs/cputimeinstate/cputimeinstate.c */ @VisibleForTesting public interface CpuTimeInStateReader { /** * Returns the overall number of cluster-frequency combinations. */ int getCpuFrequencyCount(); // Path: /proc/<pid>/stat private final String mProcessStatFilePath; /** * Returns true to indicate success. * * Called from native. */ boolean startTrackingProcessCpuTimes(int tgid); // Path: /proc/<pid>/task private final Path mThreadsDirectoryPath; /** * Returns true to indicate success. * * Called from native. */ boolean startAggregatingTaskCpuTimes(int pid, int aggregationKey); /** * Count of frequencies read from the {@code time_in_state} file. Read from {@link * #mProcTimeInStateReader#getCpuFrequenciesKhz()}. * 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. */ private int mFrequencyCount; 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; } /** Loading @@ -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; Loading @@ -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 int getCpuFrequencyCount() { if (mFrequencyCount == 0) { mFrequencyCount = mProcTimeInStateReader.getFrequenciesKhz().length; 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; } 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 * @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. */ @Nullable public ProcessCpuUsage getProcessCpuUsage(int[] selectedThreadIds) { if (DEBUG) { Slog.d(TAG, "Reading CPU thread usages with directory " + mProcPath + " process ID " + mPid); public void setSelectedThreadIds(int[] nativeTids) { mSelectedThreadNativeTids = nativeTids.clone(); if (mIsTracking) { startAggregatingThreadCpuTimes(mSelectedThreadNativeTids, mCpuTimeInStateReader); } 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; } if (!isSorted(selectedThreadIds)) { throw new IllegalArgumentException("selectedThreadIds is not sorted: " + Arrays.toString(selectedThreadIds)); /** * Get the CPU frequencies that correspond to the times reported in {@link ProcessCpuUsage}. */ public int getCpuFrequencyCount() { if (mFrequencyCount == 0) { mFrequencyCount = getCpuFrequencyCount(mCpuTimeInStateReader); } if (!Process.readProcFile(mProcessStatFilePath, PROCESS_FULL_STATS_FORMAT, null, mProcessFullStatsData, null)) { Slog.e(TAG, "Failed to read process stat file " + mProcessStatFilePath); return null; return mFrequencyCount; } long utime = mProcessFullStatsData[PROCESS_FULL_STAT_UTIME]; long stime = mProcessFullStatsData[PROCESS_FULL_STAT_STIME]; /** * 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() { if (DEBUG) { Slog.d(TAG, "Reading CPU thread usages for PID " + mPid); } long processCpuTimeMillis = (utime + stime) * mJiffyMillis; ProcessCpuUsage processCpuUsage = new ProcessCpuUsage(getCpuFrequencyCount()); 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); }