Merge "Use eBPF-based time-in-state monitoring for groups of threads"
This commit is contained in:
committed by
Android (Google) Code Review
commit
62d862082b
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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.
|
||||
*/
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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++) {
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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++) {
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
// Path: /proc/<pid>/stat
|
||||
private final String mProcessStatFilePath;
|
||||
|
||||
// Path: /proc/<pid>/task
|
||||
private final Path mThreadsDirectoryPath;
|
||||
private int[] mSelectedThreadNativeTids = new int[0]; // Sorted
|
||||
|
||||
/**
|
||||
* Count of frequencies read from the {@code time_in_state} file. Read from {@link
|
||||
* #mProcTimeInStateReader#getCpuFrequenciesKhz()}.
|
||||
* Count of frequencies read from the {@code time_in_state} file.
|
||||
*/
|
||||
private int mFrequencyCount;
|
||||
|
||||
private boolean mIsTracking;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* 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);
|
||||
Slog.d(TAG, "Reading CPU thread usages for PID " + mPid);
|
||||
}
|
||||
|
||||
int cpuFrequencyCount = getCpuFrequencyCount();
|
||||
ProcessCpuUsage processCpuUsage = new ProcessCpuUsage(cpuFrequencyCount);
|
||||
ProcessCpuUsage processCpuUsage = new ProcessCpuUsage(getCpuFrequencyCount());
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
if (!Process.readProcFile(mProcessStatFilePath, PROCESS_FULL_STATS_FORMAT, null,
|
||||
mProcessFullStatsData, null)) {
|
||||
Slog.e(TAG, "Failed to read process stat file " + mProcessStatFilePath);
|
||||
boolean result = readProcessCpuUsage(mPid,
|
||||
processCpuUsage.threadCpuTimesMillis,
|
||||
processCpuUsage.selectedThreadCpuTimesMillis,
|
||||
mCpuTimeInStateReader);
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
long utime = mProcessFullStatsData[PROCESS_FULL_STAT_UTIME];
|
||||
long stime = mProcessFullStatsData[PROCESS_FULL_STAT_STIME];
|
||||
|
||||
long processCpuTimeMillis = (utime + stime) * mJiffyMillis;
|
||||
|
||||
try (DirectoryStream<Path> threadPaths = Files.newDirectoryStream(mThreadsDirectoryPath)) {
|
||||
for (Path threadDirectory : threadPaths) {
|
||||
readThreadCpuUsage(processCpuUsage, selectedThreadIds, threadDirectory);
|
||||
}
|
||||
} catch (IOException | DirectoryIteratorException e) {
|
||||
// Expected when a process finishes
|
||||
return null;
|
||||
}
|
||||
|
||||
// Estimate per cluster per frequency CPU time for the entire process
|
||||
// by distributing the total process CPU time proportionately to how much
|
||||
// CPU time its threads took on those clusters/frequencies. This algorithm
|
||||
// works more accurately when when we have equally distributed concurrency.
|
||||
// TODO(b/169279846): obtain actual process CPU times from the kernel
|
||||
long totalCpuTimeAllThreads = 0;
|
||||
for (int i = cpuFrequencyCount - 1; i >= 0; i--) {
|
||||
totalCpuTimeAllThreads += processCpuUsage.threadCpuTimesMillis[i];
|
||||
}
|
||||
|
||||
for (int i = cpuFrequencyCount - 1; i >= 0; i--) {
|
||||
processCpuUsage.processCpuTimesMillis[i] =
|
||||
processCpuTimeMillis * processCpuUsage.threadCpuTimesMillis[i]
|
||||
/ totalCpuTimeAllThreads;
|
||||
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 readProcessCpuUsage(String procPath, int pid, int[] selectedThreadIds,
|
||||
long[] processCpuTimesMillis, long[] threadCpuTimesMillis,
|
||||
long[] selectedThreadCpuTimesMillis);
|
||||
private native boolean startTrackingProcessCpuTimes(int pid, CpuTimeInStateReader reader);
|
||||
|
||||
private native boolean startAggregatingThreadCpuTimes(int[] selectedThreadIds,
|
||||
CpuTimeInStateReader reader);
|
||||
|
||||
private native boolean readProcessCpuUsage(int pid,
|
||||
long[] threadCpuTimesMillis,
|
||||
long[] selectedThreadCpuTimesMillis,
|
||||
CpuTimeInStateReader reader);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -26,239 +26,230 @@
|
||||
#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() {}
|
||||
|
||||
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;
|
||||
}
|
||||
// Returns the overall number of cluser-frequency combinations
|
||||
virtual size_t getCpuFrequencyCount();
|
||||
|
||||
outThreadIds.reserve(threadCount);
|
||||
// Marks the CPU time-in-state tracking for threads of the specified TGID
|
||||
virtual bool startTrackingProcessCpuTimes(pid_t) = 0;
|
||||
|
||||
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);
|
||||
// Marks the thread specified by its PID for CPU time-in-state tracking.
|
||||
virtual bool startAggregatingTaskCpuTimes(pid_t, uint16_t) = 0;
|
||||
|
||||
// Retrieves the accumulated time-in-state data, which is organized as a map
|
||||
// from aggregation keys to vectors of vectors using the format:
|
||||
// { aggKey0 -> [[t0_0_0, t0_0_1, ...], [t0_1_0, t0_1_1, ...], ...],
|
||||
// aggKey1 -> [[t1_0_0, t1_0_1, ...], [t1_1_0, t1_1_1, ...], ...], ... }
|
||||
// where ti_j_k is the ns tid i spent running on the jth cluster at the cluster's kth lowest
|
||||
// freq.
|
||||
virtual std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
|
||||
getAggregatedTaskCpuFreqTimes(pid_t, const std::vector<uint16_t> &);
|
||||
};
|
||||
|
||||
// ICpuTimeInStateReader that uses eBPF to provide a map of aggregated CPU time-in-state values.
|
||||
// See cputtimeinstate.h/.cpp
|
||||
class BpfCpuTimeInStateReader : public ICpuTimeInStateReader {
|
||||
public:
|
||||
size_t getCpuFrequencyCount() {
|
||||
std::optional<std::vector<std::vector<uint32_t>>> cpuFreqs = android::bpf::getCpuFreqs();
|
||||
if (!cpuFreqs) {
|
||||
ALOGE("Cannot obtain CPU frequency count");
|
||||
return 0;
|
||||
}
|
||||
free(dirlist[i]);
|
||||
}
|
||||
free(dirlist);
|
||||
|
||||
return true;
|
||||
size_t freqCount = 0;
|
||||
for (auto cluster : *cpuFreqs) {
|
||||
freqCount += cluster.size();
|
||||
}
|
||||
|
||||
return freqCount;
|
||||
}
|
||||
|
||||
bool startTrackingProcessCpuTimes(pid_t tgid) {
|
||||
return android::bpf::startTrackingProcessCpuTimes(tgid);
|
||||
}
|
||||
|
||||
bool startAggregatingTaskCpuTimes(pid_t pid, uint16_t aggregationKey) {
|
||||
return android::bpf::startAggregatingTaskCpuTimes(pid, aggregationKey);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
static jint getCpuFrequencyCount(JNIEnv *env, jclass, jobject cpuTimeInStateReaderObject) {
|
||||
std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
|
||||
getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
|
||||
return cpuTimeInStateReader->getCpuFrequencyCount();
|
||||
}
|
||||
|
||||
if (!android::base::ReadFileToString(timeInStateFilePath, &data)) {
|
||||
ALOGE("Cannot read file: %s", timeInStateFilePath.c_str());
|
||||
return false;
|
||||
}
|
||||
static jboolean startTrackingProcessCpuTimes(JNIEnv *env, jclass, jint tgid,
|
||||
jobject cpuTimeInStateReaderObject) {
|
||||
std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
|
||||
getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
|
||||
return cpuTimeInStateReader->startTrackingProcessCpuTimes(tgid);
|
||||
}
|
||||
|
||||
auto lines = android::base::Split(data, "\n");
|
||||
size_t index = 0;
|
||||
for (const auto &line : lines) {
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
static jboolean startAggregatingThreadCpuTimes(JNIEnv *env, jclass, jintArray selectedThreadIdArray,
|
||||
jobject cpuTimeInStateReaderObject) {
|
||||
ScopedIntArrayRO selectedThreadIds(env, selectedThreadIdArray);
|
||||
std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
|
||||
getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
|
||||
|
||||
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());
|
||||
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;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < threadIds.size(); i++) {
|
||||
pid_t tid = threadIds[i];
|
||||
std::vector<uint64_t> timeInState(frequencyCount);
|
||||
if (!getThreadTimeInState(procPath, pid, tid, frequencyCount, timeInState)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool selectedThread = isSelectedThread(tid, selectedThreadIds, selectedThreadCount);
|
||||
for (size_t j = 0; j < frequencyCount; j++) {
|
||||
threadCpuTimesMillis[j] += timeInState[j];
|
||||
if (selectedThread) {
|
||||
selectedThreadCpuTimesMillis[j] += timeInState[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < frequencyCount; i++) {
|
||||
threadCpuTimesMillis[i] *= gJiffyMillis;
|
||||
selectedThreadCpuTimesMillis[i] *= gJiffyMillis;
|
||||
}
|
||||
}
|
||||
|
||||
// Reads process utime and stime from the /proc/<pid>/stat file.
|
||||
// Format of this file is described in https://man7.org/linux/man-pages/man5/proc.5.html.
|
||||
static bool getProcessCpuTime(const std::string &procPath, const pid_t pid,
|
||||
uint64_t &outTimeMillis) {
|
||||
std::string statFilePath = android::base::StringPrintf("%s/%u/stat", procPath.c_str(), pid);
|
||||
std::string data;
|
||||
if (!android::base::ReadFileToString(statFilePath, &data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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());
|
||||
return false;
|
||||
}
|
||||
|
||||
outTimeMillis = (utime + stime) * gJiffyMillis;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Estimates per cluster per frequency CPU time for the entire process
|
||||
// by distributing the total process CPU time proportionately to how much
|
||||
// CPU time its threads took on those clusters/frequencies. This algorithm
|
||||
// works more accurately when when we have equally distributed concurrency.
|
||||
// TODO(b/169279846): obtain actual process CPU times from the kernel
|
||||
static void estimateProcessTimeInState(const uint64_t processCpuTimeMillis,
|
||||
const uint64_t *threadCpuTimesMillis,
|
||||
const size_t frequencyCount,
|
||||
uint64_t *processCpuTimesMillis) {
|
||||
uint64_t totalCpuTimeAllThreads = 0;
|
||||
for (size_t i = 0; i < frequencyCount; i++) {
|
||||
totalCpuTimeAllThreads += threadCpuTimesMillis[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static jboolean readProcessCpuUsage(JNIEnv *env, jclass, jstring procPath, jint pid,
|
||||
jintArray selectedThreadIdArray,
|
||||
jlongArray processCpuTimesMillisArray,
|
||||
static jboolean readProcessCpuUsage(JNIEnv *env, jclass, jint pid,
|
||||
jlongArray threadCpuTimesMillisArray,
|
||||
jlongArray selectedThreadCpuTimesMillisArray) {
|
||||
ScopedUtfChars procPathChars(env, procPath);
|
||||
ScopedIntArrayRO selectedThreadIds(env, selectedThreadIdArray);
|
||||
ScopedLongArrayRW processCpuTimesMillis(env, processCpuTimesMillisArray);
|
||||
jlongArray selectedThreadCpuTimesMillisArray,
|
||||
jobject cpuTimeInStateReaderObject) {
|
||||
ScopedLongArrayRW threadCpuTimesMillis(env, threadCpuTimesMillisArray);
|
||||
ScopedLongArrayRW selectedThreadCpuTimesMillis(env, selectedThreadCpuTimesMillisArray);
|
||||
std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
|
||||
getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
|
||||
|
||||
std::string procPathStr(procPathChars.c_str());
|
||||
|
||||
// Get all thread IDs for the process.
|
||||
std::vector<pid_t> threadIds;
|
||||
if (!getThreadIds(procPathStr, pid, threadIds)) {
|
||||
ALOGE("Could not obtain thread IDs from: %s", procPathStr.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t frequencyCount = processCpuTimesMillis.size();
|
||||
const size_t frequencyCount = cpuTimeInStateReader->getCpuFrequencyCount();
|
||||
|
||||
if (threadCpuTimesMillis.size() != frequencyCount) {
|
||||
ALOGE("Invalid array length: threadCpuTimesMillis");
|
||||
ALOGE("Invalid threadCpuTimesMillis array length: %zu frequencies; expected: %zu",
|
||||
threadCpuTimesMillis.size(), frequencyCount);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (selectedThreadCpuTimesMillis.size() != frequencyCount) {
|
||||
ALOGE("Invalid array length: selectedThreadCpuTimesMillisArray");
|
||||
ALOGE("Invalid selectedThreadCpuTimesMillis array length: %zu frequencies; expected: %zu",
|
||||
selectedThreadCpuTimesMillis.size(), frequencyCount);
|
||||
return false;
|
||||
}
|
||||
|
||||
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()));
|
||||
for (size_t i = 0; i < frequencyCount; i++) {
|
||||
threadCpuTimesMillis[i] = 0;
|
||||
selectedThreadCpuTimesMillis[i] = 0;
|
||||
}
|
||||
return ret;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (!flattenTimeInStateData(threadCpuTimesMillis, (*data)[DEFAULT_THREAD_AGGREGATION_KEY])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!flattenTimeInStateData(selectedThreadCpuTimesMillis,
|
||||
(*data)[SELECTED_THREAD_AGGREGATION_KEY])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// threadCpuTimesMillis returns CPU times for _all_ threads, including the selected ones
|
||||
for (size_t i = 0; i < frequencyCount; i++) {
|
||||
threadCpuTimesMillis[i] += selectedThreadCpuTimesMillis[i];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const JNINativeMethod g_single_methods[] = {
|
||||
{"readProcessCpuUsage", "(Ljava/lang/String;I[I[J[J[J)Z", (void *)readProcessCpuUsage},
|
||||
{"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},
|
||||
};
|
||||
|
||||
int register_com_android_internal_os_KernelSingleProcessCpuThreadReader(JNIEnv *env) {
|
||||
@@ -266,4 +257,77 @@ int register_com_android_internal_os_KernelSingleProcessCpuThreadReader(JNIEnv *
|
||||
g_single_methods, NELEM(g_single_methods));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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 {};
|
||||
}
|
||||
|
||||
std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>> map;
|
||||
|
||||
jobjectArray stringArray =
|
||||
(jobjectArray)mEnv->CallObjectMethod(mCpuTimeInStateReader, mid, pid);
|
||||
int size = mEnv->GetArrayLength(stringArray);
|
||||
for (int i = 0; i < size; i++) {
|
||||
ScopedUtfChars line(mEnv, (jstring)mEnv->GetObjectArrayElement(stringArray, i));
|
||||
uint16_t aggregationKey;
|
||||
std::vector<std::vector<uint64_t>> times;
|
||||
|
||||
// Each string is formatted like this: "aggKey:t0_0 t0_1...:t1_0 t1_1..."
|
||||
auto fields = android::base::Split(line.c_str(), ":");
|
||||
android::base::ParseUint(fields[0], &aggregationKey);
|
||||
|
||||
for (int j = 1; j < fields.size(); j++) {
|
||||
auto numbers = android::base::Split(fields[j], " ");
|
||||
|
||||
std::vector<uint64_t> chunk;
|
||||
for (int k = 0; k < numbers.size(); k++) {
|
||||
uint64_t time;
|
||||
android::base::ParseUint(numbers[k], &time);
|
||||
chunk.emplace_back(time);
|
||||
}
|
||||
times.emplace_back(chunk);
|
||||
}
|
||||
|
||||
map.emplace(aggregationKey, times);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
|
||||
@@ -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 {
|
||||
public static class MockCpuTimeInStateReader implements
|
||||
KernelSingleProcessCpuThreadReader.CpuTimeInStateReader {
|
||||
private final int mCpuFrequencyCount;
|
||||
private final String[] mAggregatedTaskCpuFreqTimes;
|
||||
public int mTrackedTgid;
|
||||
public List<String> mTrackedTasks = new ArrayList<>();
|
||||
|
||||
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 MockCpuTimeInStateReader(int cpuFrequencyCount,
|
||||
String[] aggregatedTaskCpuFreqTimes) {
|
||||
mCpuFrequencyCount = cpuFrequencyCount;
|
||||
mAggregatedTaskCpuFreqTimes = aggregatedTaskCpuFreqTimes;
|
||||
}
|
||||
|
||||
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());
|
||||
@Override
|
||||
public int getCpuFrequencyCount() {
|
||||
return mCpuFrequencyCount;
|
||||
}
|
||||
|
||||
// Make /proc/$PID/task
|
||||
final Path selfThreadsPath = processPath.resolve("task");
|
||||
assertTrue(selfThreadsPath.toFile().mkdirs());
|
||||
@Override
|
||||
public boolean startTrackingProcessCpuTimes(int tgid) {
|
||||
mTrackedTgid = tgid;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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());
|
||||
public boolean startAggregatingTaskCpuTimes(int pid, int aggregationKey) {
|
||||
mTrackedTasks.add(pid + " " + aggregationKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
reader.setBinderThreadNativeTids(new int[] {1, 3});
|
||||
SystemServerCpuThreadReader.SystemServiceCpuThreadTimes systemServiceCpuThreadTimes =
|
||||
reader.readDelta();
|
||||
assertArrayEquals(new long[] {100 * 10000, 1100 * 10000},
|
||||
systemServiceCpuThreadTimes.threadCpuTimesUs);
|
||||
assertArrayEquals(new long[] {0, 600 * 10000},
|
||||
systemServiceCpuThreadTimes.binderThreadCpuTimesUs);
|
||||
}
|
||||
MockCpuTimeInStateReader mockReader = new MockCpuTimeInStateReader(4);
|
||||
// Units are nanoseconds
|
||||
mockReader.setAggregatedTaskCpuFreqTimes(new String[] {
|
||||
"0:1000000000 2000000000 3000000000:4000000000",
|
||||
"1:100000000 200000000 300000000:400000000",
|
||||
});
|
||||
|
||||
@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);
|
||||
SystemServerCpuThreadReader reader = new SystemServerCpuThreadReader(pid, mockReader);
|
||||
reader.setBinderThreadNativeTids(new int[] {1, 3});
|
||||
|
||||
// First time, populate "last" snapshot
|
||||
reader.readDelta();
|
||||
|
||||
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});
|
||||
|
||||
// Second time, get the actual delta
|
||||
// The first invocation of readDelta populates the "last" snapshot
|
||||
SystemServerCpuThreadReader.SystemServiceCpuThreadTimes 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[] {1100000, 2200000, 3300000, 4400000});
|
||||
assertThat(systemServiceCpuThreadTimes.binderThreadCpuTimesUs)
|
||||
.isEqualTo(new long[] {100000, 200000, 300000, 400000});
|
||||
|
||||
mockReader.setAggregatedTaskCpuFreqTimes(new String[] {
|
||||
"0:1010000000 2020000000 3030000000:4040000000",
|
||||
"1:101000000 202000000 303000000:404000000",
|
||||
});
|
||||
|
||||
// The second invocation gets the actual delta
|
||||
systemServiceCpuThreadTimes = reader.readDelta();
|
||||
|
||||
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());
|
||||
|
||||
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());
|
||||
}
|
||||
MockCpuTimeInStateReader(int frequencyCount) {
|
||||
mCpuFrequencyCount = frequencyCount;
|
||||
}
|
||||
|
||||
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());
|
||||
@Override
|
||||
public int getCpuFrequencyCount() {
|
||||
return mCpuFrequencyCount;
|
||||
}
|
||||
|
||||
// Make /proc/$PID/task
|
||||
final Path selfThreadsPath = processPath.resolve("task");
|
||||
assertTrue(selfThreadsPath.toFile().mkdirs());
|
||||
@Override
|
||||
public boolean startTrackingProcessCpuTimes(int tgid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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());
|
||||
public boolean startAggregatingTaskCpuTimes(int pid, int aggregationKey) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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 void setAggregatedTaskCpuFreqTimes(String[] mAggregatedTaskCpuFreqTimes) {
|
||||
this.mAggregatedTaskCpuFreqTimes = mAggregatedTaskCpuFreqTimes;
|
||||
}
|
||||
|
||||
public String[] getAggregatedTaskCpuFreqTimes(int pid) {
|
||||
return mAggregatedTaskCpuFreqTimes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,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});
|
||||
|
||||
@@ -146,9 +145,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;
|
||||
}
|
||||
|
||||
@@ -261,6 +261,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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user