blob: dfae68429f6d12e18faa36328d1e4f39ee685199 [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.
*/
#include "core_jni_helpers.h"
#include <cputimeinstate.h>
#include <dirent.h>
#include <android-base/file.h>
#include <android-base/parseint.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android_runtime/Log.h>
#include <nativehelper/ScopedPrimitiveArray.h>
namespace android {
static constexpr uint16_t DEFAULT_THREAD_AGGREGATION_KEY = 0;
static constexpr uint16_t SELECTED_THREAD_AGGREGATION_KEY = 1;
static constexpr uint64_t NSEC_PER_MSEC = 1000000;
// Number of milliseconds in a jiffy - the unit of time measurement for processes and threads
static const uint32_t gJiffyMillis = (uint32_t)(1000 / sysconf(_SC_CLK_TCK));
// Abstract class for readers of CPU time-in-state. There are two implementations of
// this class: BpfCpuTimeInStateReader and MockCpuTimeInStateReader. The former is used
// by the production code. The latter is used by unit tests to provide mock
// CPU time-in-state data via a Java implementation.
class ICpuTimeInStateReader {
public:
virtual ~ICpuTimeInStateReader() {}
// Returns the overall number of cluser-frequency combinations
virtual size_t getCpuFrequencyCount();
// Marks the CPU time-in-state tracking for threads of the specified TGID
virtual bool startTrackingProcessCpuTimes(pid_t) = 0;
// Marks the thread specified by its PID for CPU time-in-state tracking.
virtual bool startAggregatingTaskCpuTimes(pid_t, uint16_t) = 0;
// Retrieves the accumulated time-in-state data, which is organized as a map
// from aggregation keys to vectors of vectors using the format:
// { aggKey0 -> [[t0_0_0, t0_0_1, ...], [t0_1_0, t0_1_1, ...], ...],
// aggKey1 -> [[t1_0_0, t1_0_1, ...], [t1_1_0, t1_1_1, ...], ...], ... }
// where ti_j_k is the ns tid i spent running on the jth cluster at the cluster's kth lowest
// freq.
virtual std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
getAggregatedTaskCpuFreqTimes(pid_t, const std::vector<uint16_t> &);
};
// ICpuTimeInStateReader that uses eBPF to provide a map of aggregated CPU time-in-state values.
// See cputtimeinstate.h/.cpp
class BpfCpuTimeInStateReader : public ICpuTimeInStateReader {
public:
size_t getCpuFrequencyCount() {
std::optional<std::vector<std::vector<uint32_t>>> cpuFreqs = android::bpf::getCpuFreqs();
if (!cpuFreqs) {
ALOGE("Cannot obtain CPU frequency count");
return 0;
}
size_t freqCount = 0;
for (auto cluster : *cpuFreqs) {
freqCount += cluster.size();
}
return freqCount;
}
bool startTrackingProcessCpuTimes(pid_t tgid) {
return android::bpf::startTrackingProcessCpuTimes(tgid);
}
bool startAggregatingTaskCpuTimes(pid_t pid, uint16_t aggregationKey) {
return android::bpf::startAggregatingTaskCpuTimes(pid, aggregationKey);
}
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();
}
}
static jint getCpuFrequencyCount(JNIEnv *env, jclass, jobject cpuTimeInStateReaderObject) {
std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
return cpuTimeInStateReader->getCpuFrequencyCount();
}
static jboolean startTrackingProcessCpuTimes(JNIEnv *env, jclass, jint tgid,
jobject cpuTimeInStateReaderObject) {
std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
return cpuTimeInStateReader->startTrackingProcessCpuTimes(tgid);
}
static jboolean startAggregatingThreadCpuTimes(JNIEnv *env, jclass, jintArray selectedThreadIdArray,
jobject cpuTimeInStateReaderObject) {
ScopedIntArrayRO selectedThreadIds(env, selectedThreadIdArray);
std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
for (int i = 0; i < selectedThreadIds.size(); i++) {
if (!cpuTimeInStateReader->startAggregatingTaskCpuTimes(selectedThreadIds[i],
SELECTED_THREAD_AGGREGATION_KEY)) {
return false;
}
}
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("CPU time-in-state reader returned data for %zu frequencies; expected: %zu", index,
frequencyCount);
return false;
}
return true;
}
// Reads all CPU time-in-state data accumulated by BPF and aggregates per-frequency
// time in state data for all threads. Also, separately aggregates time in state for
// selected threads whose TIDs are passes as selectedThreadIds.
static jboolean readProcessCpuUsage(JNIEnv *env, jclass, jint pid,
jlongArray threadCpuTimesMillisArray,
jlongArray selectedThreadCpuTimesMillisArray,
jobject cpuTimeInStateReaderObject) {
ScopedLongArrayRW threadCpuTimesMillis(env, threadCpuTimesMillisArray);
ScopedLongArrayRW selectedThreadCpuTimesMillis(env, selectedThreadCpuTimesMillisArray);
std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
const size_t frequencyCount = cpuTimeInStateReader->getCpuFrequencyCount();
if (threadCpuTimesMillis.size() != frequencyCount) {
ALOGE("Invalid threadCpuTimesMillis array length: %zu frequencies; expected: %zu",
threadCpuTimesMillis.size(), frequencyCount);
return false;
}
if (selectedThreadCpuTimesMillis.size() != frequencyCount) {
ALOGE("Invalid selectedThreadCpuTimesMillis array length: %zu frequencies; expected: %zu",
selectedThreadCpuTimesMillis.size(), frequencyCount);
return false;
}
for (size_t i = 0; i < frequencyCount; i++) {
threadCpuTimesMillis[i] = 0;
selectedThreadCpuTimesMillis[i] = 0;
}
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[] = {
{"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) {
return RegisterMethodsOrDie(env, "com/android/internal/os/KernelSingleProcessCpuThreadReader",
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