1 /*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define LOG_TAG "CpuPowerStatsCollector"
18
19 #include <cputimeinstate.h>
20 #include <log/log.h>
21 #include <nativehelper/ScopedPrimitiveArray.h>
22
23 #include "core_jni_helpers.h"
24
25 #define EXCEPTION (-1)
26
27 namespace android {
28
29 #define JAVA_CLASS_CPU_POWER_STATS_COLLECTOR "com/android/server/power/stats/CpuPowerStatsCollector"
30 #define JAVA_CLASS_KERNEL_CPU_STATS_READER \
31 JAVA_CLASS_CPU_POWER_STATS_COLLECTOR "$KernelCpuStatsReader"
32 #define JAVA_CLASS_KERNEL_CPU_STATS_CALLBACK \
33 JAVA_CLASS_CPU_POWER_STATS_COLLECTOR "$KernelCpuStatsCallback"
34
35 static constexpr uint64_t NSEC_PER_MSEC = 1000000;
36
37 static int flatten(JNIEnv *env, const std::vector<std::vector<uint64_t>> ×,
38 jlongArray outArray);
39
40 static int combineByBracket(JNIEnv *env, const std::vector<std::vector<uint64_t>> ×,
41 ScopedIntArrayRO &scopedScalingStepToPowerBracketMap,
42 jlongArray outBrackets);
43
44 static bool initialized = false;
45 static jclass class_KernelCpuStatsCallback;
46 static jmethodID method_KernelCpuStatsCallback_processUidStats;
47
init(JNIEnv * env)48 static int init(JNIEnv *env) {
49 jclass temp = env->FindClass(JAVA_CLASS_KERNEL_CPU_STATS_CALLBACK);
50 class_KernelCpuStatsCallback = (jclass)env->NewGlobalRef(temp);
51 if (!class_KernelCpuStatsCallback) {
52 jniThrowExceptionFmt(env, "java/lang/ClassNotFoundException",
53 "Class not found: " JAVA_CLASS_KERNEL_CPU_STATS_CALLBACK);
54 return EXCEPTION;
55 }
56 method_KernelCpuStatsCallback_processUidStats =
57 env->GetMethodID(class_KernelCpuStatsCallback, "processUidStats", "(I[J)V");
58 if (!method_KernelCpuStatsCallback_processUidStats) {
59 jniThrowExceptionFmt(env, "java/lang/NoSuchMethodException",
60 "Method not found: " JAVA_CLASS_KERNEL_CPU_STATS_CALLBACK
61 ".processUidStats");
62 return EXCEPTION;
63 }
64 initialized = true;
65 return OK;
66 }
67
nativeIsSupportedFeature(JNIEnv * env)68 static jboolean nativeIsSupportedFeature(JNIEnv *env) {
69 if (!android::bpf::startTrackingUidTimes()) {
70 return false;
71 }
72 auto totalByScalingStep = android::bpf::getTotalCpuFreqTimes();
73 return totalByScalingStep.has_value();
74 }
75
nativeReadCpuStats(JNIEnv * env,jobject zis,jobject callback,jintArray scalingStepToPowerBracketMap,jlong lastUpdateTimestampNanos,jlongArray cpuTimeByScalingStep,jlongArray tempForUidStats)76 static jlong nativeReadCpuStats(JNIEnv *env, [[maybe_unused]] jobject zis, jobject callback,
77 jintArray scalingStepToPowerBracketMap,
78 jlong lastUpdateTimestampNanos, jlongArray cpuTimeByScalingStep,
79 jlongArray tempForUidStats) {
80 ScopedIntArrayRO scopedScalingStepToPowerBracketMap(env, scalingStepToPowerBracketMap);
81
82 if (!initialized) {
83 if (init(env) == EXCEPTION) {
84 return 0L;
85 }
86 }
87
88 auto totalByScalingStep = android::bpf::getTotalCpuFreqTimes();
89 if (!totalByScalingStep) {
90 jniThrowExceptionFmt(env, "java/lang/RuntimeException", "Unsupported kernel feature");
91 return EXCEPTION;
92 }
93
94 if (flatten(env, *totalByScalingStep, cpuTimeByScalingStep) == EXCEPTION) {
95 return 0L;
96 }
97
98 uint64_t newLastUpdate = lastUpdateTimestampNanos;
99 auto data = android::bpf::getUidsUpdatedCpuFreqTimes(&newLastUpdate);
100 if (!data.has_value()) return lastUpdateTimestampNanos;
101
102 for (auto &[uid, times] : *data) {
103 if (combineByBracket(env, times, scopedScalingStepToPowerBracketMap, tempForUidStats) ==
104 EXCEPTION) {
105 return 0L;
106 }
107 env->CallVoidMethod(callback, method_KernelCpuStatsCallback_processUidStats, (jint)uid,
108 tempForUidStats);
109 }
110 return newLastUpdate;
111 }
112
flatten(JNIEnv * env,const std::vector<std::vector<uint64_t>> & times,jlongArray outArray)113 static int flatten(JNIEnv *env, const std::vector<std::vector<uint64_t>> ×,
114 jlongArray outArray) {
115 ScopedLongArrayRW scopedOutArray(env, outArray);
116 const uint8_t scalingStepCount = scopedOutArray.size();
117 uint64_t *out = reinterpret_cast<uint64_t *>(scopedOutArray.get());
118 uint32_t scalingStep = 0;
119 for (const auto &subVec : times) {
120 for (uint32_t i = 0; i < subVec.size(); ++i) {
121 if (scalingStep >= scalingStepCount) {
122 jniThrowExceptionFmt(env, "java/lang/IndexOutOfBoundsException",
123 "Array is too short, size=%u, scalingStep=%u",
124 scalingStepCount, scalingStep);
125 return EXCEPTION;
126 }
127 out[scalingStep] = subVec[i] / NSEC_PER_MSEC;
128 scalingStep++;
129 }
130 }
131 return OK;
132 }
133
combineByBracket(JNIEnv * env,const std::vector<std::vector<uint64_t>> & times,ScopedIntArrayRO & scopedScalingStepToPowerBracketMap,jlongArray outBrackets)134 static int combineByBracket(JNIEnv *env, const std::vector<std::vector<uint64_t>> ×,
135 ScopedIntArrayRO &scopedScalingStepToPowerBracketMap,
136 jlongArray outBrackets) {
137 ScopedLongArrayRW scopedOutBrackets(env, outBrackets);
138 uint64_t *brackets = reinterpret_cast<uint64_t *>(scopedOutBrackets.get());
139 const uint8_t statsSize = scopedOutBrackets.size();
140 memset(brackets, 0, statsSize * sizeof(uint64_t));
141 const uint8_t scalingStepCount = scopedScalingStepToPowerBracketMap.size();
142
143 uint32_t scalingStep = 0;
144 for (const auto &subVec : times) {
145 for (uint32_t i = 0; i < subVec.size(); ++i) {
146 if (scalingStep >= scalingStepCount) {
147 jniThrowExceptionFmt(env, "java/lang/IndexOutOfBoundsException",
148 "scalingStepToPowerBracketMap is too short, "
149 "size=%u, scalingStep=%u",
150 scalingStepCount, scalingStep);
151 return EXCEPTION;
152 }
153 uint32_t bracket = scopedScalingStepToPowerBracketMap[scalingStep];
154 if (bracket >= statsSize) {
155 jniThrowExceptionFmt(env, "java/lang/IndexOutOfBoundsException",
156 "Bracket array is too short, length=%u, bracket[%u]=%u",
157 statsSize, scalingStep, bracket);
158 return EXCEPTION;
159 }
160 brackets[bracket] += subVec[i] / NSEC_PER_MSEC;
161 scalingStep++;
162 }
163 }
164 return OK;
165 }
166
167 static const JNINativeMethod method_table[] = {
168 {"nativeIsSupportedFeature", "()Z", (void *)nativeIsSupportedFeature},
169 {"nativeReadCpuStats", "(L" JAVA_CLASS_KERNEL_CPU_STATS_CALLBACK ";[IJ[J[J)J",
170 (void *)nativeReadCpuStats},
171 };
172
register_android_server_power_stats_CpuPowerStatsCollector(JNIEnv * env)173 int register_android_server_power_stats_CpuPowerStatsCollector(JNIEnv *env) {
174 return jniRegisterNativeMethods(env, JAVA_CLASS_KERNEL_CPU_STATS_READER, method_table,
175 NELEM(method_table));
176 }
177
178 } // namespace android
179