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>> &times,
38                    jlongArray outArray);
39 
40 static int combineByBracket(JNIEnv *env, const std::vector<std::vector<uint64_t>> &times,
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>> &times,
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>> &times,
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