/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp
C++ | 333 lines | 236 code | 56 blank | 41 comment | 31 complexity | 4bb96bf1361091f6bfdc5798b560c2ac MD5 | raw file
Possible License(s): Apache-2.0
- /*
- * 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