blob: 4d2a08a4bcf35d1337c816796e6595d119c04ad9 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.os;
import android.annotation.Nullable;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import java.io.IOException;
import java.util.Arrays;
/**
* Iterates over all threads owned by a given process, and return the CPU usage for
* each thread. The CPU usage statistics contain the amount of time spent in a frequency band. CPU
* usage is collected using {@link ProcTimeInStateReader}.
*/
public class KernelSingleProcessCpuThreadReader {
private static final String TAG = "KernelSingleProcCpuThreadRdr";
private static final boolean DEBUG = false;
private final int mPid;
private final CpuTimeInStateReader mCpuTimeInStateReader;
private int[] mSelectedThreadNativeTids = new int[0]; // Sorted
/**
* 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.
*/
@VisibleForTesting
public KernelSingleProcessCpuThreadReader(int pid,
@Nullable CpuTimeInStateReader cpuTimeInStateReader) throws IOException {
mPid = pid;
mCpuTimeInStateReader = cpuTimeInStateReader;
}
/**
* Create the reader and handle exceptions during creation
*
* @return the reader, null if an exception was thrown during creation
*/
@Nullable
public static KernelSingleProcessCpuThreadReader create(int pid) {
try {
return new KernelSingleProcessCpuThreadReader(pid, null);
} catch (IOException e) {
Slog.e(TAG, "Failed to initialize KernelSingleProcessCpuThreadReader", e);
return null;
}
}
/**
* 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 = getCpuFrequencyCount(mCpuTimeInStateReader);
}
return mFrequencyCount;
}
/**
* Get the total CPU usage of the process with the PID specified in the
* constructor. The CPU usage time is aggregated across all threads and may
* exceed the time the entire process has been running.
*/
@Nullable
public ProcessCpuUsage getProcessCpuUsage() {
if (DEBUG) {
Slog.d(TAG, "Reading CPU thread usages for PID " + mPid);
}
ProcessCpuUsage processCpuUsage = new ProcessCpuUsage(getCpuFrequencyCount());
boolean result = readProcessCpuUsage(mPid,
processCpuUsage.threadCpuTimesMillis,
processCpuUsage.selectedThreadCpuTimesMillis,
mCpuTimeInStateReader);
if (!result) {
return null;
}
if (DEBUG) {
Slog.d(TAG, "threadCpuTimesMillis = "
+ Arrays.toString(processCpuUsage.threadCpuTimesMillis));
Slog.d(TAG, "selectedThreadCpuTimesMillis = "
+ Arrays.toString(processCpuUsage.selectedThreadCpuTimesMillis));
}
return processCpuUsage;
}
/** CPU usage of a process, all of its threads and a selected subset of its threads */
public static class ProcessCpuUsage {
public long[] threadCpuTimesMillis;
public long[] selectedThreadCpuTimesMillis;
public ProcessCpuUsage(int cpuFrequencyCount) {
threadCpuTimesMillis = new long[cpuFrequencyCount];
selectedThreadCpuTimesMillis = new long[cpuFrequencyCount];
}
}
private native int getCpuFrequencyCount(CpuTimeInStateReader reader);
private native boolean startTrackingProcessCpuTimes(int pid, CpuTimeInStateReader reader);
private native boolean startAggregatingThreadCpuTimes(int[] selectedThreadIds,
CpuTimeInStateReader reader);
private native boolean readProcessCpuUsage(int pid,
long[] threadCpuTimesMillis,
long[] selectedThreadCpuTimesMillis,
CpuTimeInStateReader reader);
}