1 /*
2  * Copyright (C) 2020 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 #include "core_jni_helpers.h"
18 
19 #include <cputimeinstate.h>
20 #include <dirent.h>
21 
22 #include <android-base/file.h>
23 #include <android-base/parseint.h>
24 #include <android-base/stringprintf.h>
25 #include <android-base/strings.h>
26 #include <android_runtime/Log.h>
27 
28 #include <nativehelper/ScopedPrimitiveArray.h>
29 
30 namespace android {
31 
32 static constexpr uint16_t DEFAULT_THREAD_AGGREGATION_KEY = 0;
33 static constexpr uint16_t SELECTED_THREAD_AGGREGATION_KEY = 1;
34 
35 static constexpr uint64_t NSEC_PER_MSEC = 1000000;
36 
37 // Number of milliseconds in a jiffy - the unit of time measurement for processes and threads
38 static const uint32_t gJiffyMillis = (uint32_t)(1000 / sysconf(_SC_CLK_TCK));
39 
40 // Abstract class for readers of CPU time-in-state. There are two implementations of
41 // this class: BpfCpuTimeInStateReader and MockCpuTimeInStateReader.  The former is used
42 // by the production code. The latter is used by unit tests to provide mock
43 // CPU time-in-state data via a Java implementation.
44 class ICpuTimeInStateReader {
45 public:
~ICpuTimeInStateReader()46     virtual ~ICpuTimeInStateReader() {}
47 
48     // Returns the overall number of cluser-frequency combinations
49     virtual size_t getCpuFrequencyCount();
50 
51     // Marks the CPU time-in-state tracking for threads of the specified TGID
52     virtual bool startTrackingProcessCpuTimes(pid_t) = 0;
53 
54     // Marks the thread specified by its PID for CPU time-in-state tracking.
55     virtual bool startAggregatingTaskCpuTimes(pid_t, uint16_t) = 0;
56 
57     // Retrieves the accumulated time-in-state data, which is organized as a map
58     // from aggregation keys to vectors of vectors using the format:
59     // { aggKey0 -> [[t0_0_0, t0_0_1, ...], [t0_1_0, t0_1_1, ...], ...],
60     //   aggKey1 -> [[t1_0_0, t1_0_1, ...], [t1_1_0, t1_1_1, ...], ...], ... }
61     // where ti_j_k is the ns tid i spent running on the jth cluster at the cluster's kth lowest
62     // freq.
63     virtual std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
64     getAggregatedTaskCpuFreqTimes(pid_t, const std::vector<uint16_t> &);
65 };
66 
67 // ICpuTimeInStateReader that uses eBPF to provide a map of aggregated CPU time-in-state values.
68 // See cputtimeinstate.h/.cpp
69 class BpfCpuTimeInStateReader : public ICpuTimeInStateReader {
70 public:
getCpuFrequencyCount()71     size_t getCpuFrequencyCount() {
72         std::optional<std::vector<std::vector<uint32_t>>> cpuFreqs = android::bpf::getCpuFreqs();
73         if (!cpuFreqs) {
74             ALOGE("Cannot obtain CPU frequency count");
75             return 0;
76         }
77 
78         size_t freqCount = 0;
79         for (auto cluster : *cpuFreqs) {
80             freqCount += cluster.size();
81         }
82 
83         return freqCount;
84     }
85 
startTrackingProcessCpuTimes(pid_t tgid)86     bool startTrackingProcessCpuTimes(pid_t tgid) {
87         return android::bpf::startTrackingProcessCpuTimes(tgid);
88     }
89 
startAggregatingTaskCpuTimes(pid_t pid,uint16_t aggregationKey)90     bool startAggregatingTaskCpuTimes(pid_t pid, uint16_t aggregationKey) {
91         return android::bpf::startAggregatingTaskCpuTimes(pid, aggregationKey);
92     }
93 
94     std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
getAggregatedTaskCpuFreqTimes(pid_t pid,const std::vector<uint16_t> & aggregationKeys)95     getAggregatedTaskCpuFreqTimes(pid_t pid, const std::vector<uint16_t> &aggregationKeys) {
96         return android::bpf::getAggregatedTaskCpuFreqTimes(pid, aggregationKeys);
97     }
98 };
99 
100 // ICpuTimeInStateReader that uses JNI to provide a map of aggregated CPU time-in-state
101 // values.
102 // This version of CpuTimeInStateReader is used exclusively for providing mock data in tests.
103 class MockCpuTimeInStateReader : public ICpuTimeInStateReader {
104 private:
105     JNIEnv *mEnv;
106     jobject mCpuTimeInStateReader;
107 
108 public:
MockCpuTimeInStateReader(JNIEnv * env,jobject cpuTimeInStateReader)109     MockCpuTimeInStateReader(JNIEnv *env, jobject cpuTimeInStateReader)
110           : mEnv(env), mCpuTimeInStateReader(cpuTimeInStateReader) {}
111 
112     size_t getCpuFrequencyCount();
113 
114     bool startTrackingProcessCpuTimes(pid_t tgid);
115 
116     bool startAggregatingTaskCpuTimes(pid_t pid, uint16_t aggregationKey);
117 
118     std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
119     getAggregatedTaskCpuFreqTimes(pid_t tgid, const std::vector<uint16_t> &aggregationKeys);
120 };
121 
getCpuTimeInStateReader(JNIEnv * env,jobject cpuTimeInStateReaderObject)122 static ICpuTimeInStateReader *getCpuTimeInStateReader(JNIEnv *env,
123                                                       jobject cpuTimeInStateReaderObject) {
124     if (cpuTimeInStateReaderObject) {
125         return new MockCpuTimeInStateReader(env, cpuTimeInStateReaderObject);
126     } else {
127         return new BpfCpuTimeInStateReader();
128     }
129 }
130 
getCpuFrequencyCount(JNIEnv * env,jclass,jobject cpuTimeInStateReaderObject)131 static jint getCpuFrequencyCount(JNIEnv *env, jclass, jobject cpuTimeInStateReaderObject) {
132     std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
133             getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
134     return cpuTimeInStateReader->getCpuFrequencyCount();
135 }
136 
startTrackingProcessCpuTimes(JNIEnv * env,jclass,jint tgid,jobject cpuTimeInStateReaderObject)137 static jboolean startTrackingProcessCpuTimes(JNIEnv *env, jclass, jint tgid,
138                                              jobject cpuTimeInStateReaderObject) {
139     std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
140             getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
141     return cpuTimeInStateReader->startTrackingProcessCpuTimes(tgid);
142 }
143 
startAggregatingThreadCpuTimes(JNIEnv * env,jclass,jintArray selectedThreadIdArray,jobject cpuTimeInStateReaderObject)144 static jboolean startAggregatingThreadCpuTimes(JNIEnv *env, jclass, jintArray selectedThreadIdArray,
145                                                jobject cpuTimeInStateReaderObject) {
146     ScopedIntArrayRO selectedThreadIds(env, selectedThreadIdArray);
147     std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
148             getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
149 
150     for (int i = 0; i < selectedThreadIds.size(); i++) {
151         if (!cpuTimeInStateReader->startAggregatingTaskCpuTimes(selectedThreadIds[i],
152                                                                 SELECTED_THREAD_AGGREGATION_KEY)) {
153             return false;
154         }
155     }
156     return true;
157 }
158 
159 // Converts time-in-state data from a vector of vectors to a flat array.
160 // Also converts from nanoseconds to milliseconds.
flattenTimeInStateData(ScopedLongArrayRW & cpuTimesMillis,const std::vector<std::vector<uint64_t>> & data)161 static bool flattenTimeInStateData(ScopedLongArrayRW &cpuTimesMillis,
162                                    const std::vector<std::vector<uint64_t>> &data) {
163     size_t frequencyCount = cpuTimesMillis.size();
164     size_t index = 0;
165     for (const auto &cluster : data) {
166         for (const uint64_t &timeNanos : cluster) {
167             if (index < frequencyCount) {
168                 cpuTimesMillis[index] = timeNanos / NSEC_PER_MSEC;
169             }
170             index++;
171         }
172     }
173     if (index != frequencyCount) {
174         ALOGE("CPU time-in-state reader returned data for %zu frequencies; expected: %zu", index,
175               frequencyCount);
176         return false;
177     }
178 
179     return true;
180 }
181 
182 // Reads all CPU time-in-state data accumulated by BPF and aggregates per-frequency
183 // time in state data for all threads.  Also, separately aggregates time in state for
184 // selected threads whose TIDs are passes as selectedThreadIds.
readProcessCpuUsage(JNIEnv * env,jclass,jint pid,jlongArray threadCpuTimesMillisArray,jlongArray selectedThreadCpuTimesMillisArray,jobject cpuTimeInStateReaderObject)185 static jboolean readProcessCpuUsage(JNIEnv *env, jclass, jint pid,
186                                     jlongArray threadCpuTimesMillisArray,
187                                     jlongArray selectedThreadCpuTimesMillisArray,
188                                     jobject cpuTimeInStateReaderObject) {
189     ScopedLongArrayRW threadCpuTimesMillis(env, threadCpuTimesMillisArray);
190     ScopedLongArrayRW selectedThreadCpuTimesMillis(env, selectedThreadCpuTimesMillisArray);
191     std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
192             getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
193 
194     const size_t frequencyCount = cpuTimeInStateReader->getCpuFrequencyCount();
195 
196     if (threadCpuTimesMillis.size() != frequencyCount) {
197         ALOGE("Invalid threadCpuTimesMillis array length: %zu frequencies; expected: %zu",
198               threadCpuTimesMillis.size(), frequencyCount);
199         return false;
200     }
201 
202     if (selectedThreadCpuTimesMillis.size() != frequencyCount) {
203         ALOGE("Invalid selectedThreadCpuTimesMillis array length: %zu frequencies; expected: %zu",
204               selectedThreadCpuTimesMillis.size(), frequencyCount);
205         return false;
206     }
207 
208     for (size_t i = 0; i < frequencyCount; i++) {
209         threadCpuTimesMillis[i] = 0;
210         selectedThreadCpuTimesMillis[i] = 0;
211     }
212 
213     std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>> data =
214             cpuTimeInStateReader->getAggregatedTaskCpuFreqTimes(pid,
215                                                                 {DEFAULT_THREAD_AGGREGATION_KEY,
216                                                                  SELECTED_THREAD_AGGREGATION_KEY});
217     if (!data) {
218         ALOGE("Cannot read thread CPU times for PID %d", pid);
219         return false;
220     }
221 
222     if (!flattenTimeInStateData(threadCpuTimesMillis, (*data)[DEFAULT_THREAD_AGGREGATION_KEY])) {
223         return false;
224     }
225 
226     if (!flattenTimeInStateData(selectedThreadCpuTimesMillis,
227                                 (*data)[SELECTED_THREAD_AGGREGATION_KEY])) {
228         return false;
229     }
230 
231     // threadCpuTimesMillis returns CPU times for _all_ threads, including the selected ones
232     for (size_t i = 0; i < frequencyCount; i++) {
233         threadCpuTimesMillis[i] += selectedThreadCpuTimesMillis[i];
234     }
235 
236     return true;
237 }
238 
239 static const JNINativeMethod g_single_methods[] = {
240         {"getCpuFrequencyCount",
241          "(Lcom/android/internal/os/KernelSingleProcessCpuThreadReader$CpuTimeInStateReader;)I",
242          (void *)getCpuFrequencyCount},
243         {"startTrackingProcessCpuTimes",
244          "(ILcom/android/internal/os/KernelSingleProcessCpuThreadReader$CpuTimeInStateReader;)Z",
245          (void *)startTrackingProcessCpuTimes},
246         {"startAggregatingThreadCpuTimes",
247          "([ILcom/android/internal/os/KernelSingleProcessCpuThreadReader$CpuTimeInStateReader;)Z",
248          (void *)startAggregatingThreadCpuTimes},
249         {"readProcessCpuUsage",
250          "(I[J[J"
251          "Lcom/android/internal/os/KernelSingleProcessCpuThreadReader$CpuTimeInStateReader;)Z",
252          (void *)readProcessCpuUsage},
253 };
254 
register_com_android_internal_os_KernelSingleProcessCpuThreadReader(JNIEnv * env)255 int register_com_android_internal_os_KernelSingleProcessCpuThreadReader(JNIEnv *env) {
256     return RegisterMethodsOrDie(env, "com/android/internal/os/KernelSingleProcessCpuThreadReader",
257                                 g_single_methods, NELEM(g_single_methods));
258 }
259 
getCpuFrequencyCount()260 size_t MockCpuTimeInStateReader::getCpuFrequencyCount() {
261     jclass cls = mEnv->GetObjectClass(mCpuTimeInStateReader);
262     jmethodID mid = mEnv->GetMethodID(cls, "getCpuFrequencyCount", "()I");
263     if (mid == 0) {
264         ALOGE("Couldn't find the method getCpuFrequencyCount");
265         return false;
266     }
267     return (size_t)mEnv->CallIntMethod(mCpuTimeInStateReader, mid);
268 }
269 
startTrackingProcessCpuTimes(pid_t tgid)270 bool MockCpuTimeInStateReader::startTrackingProcessCpuTimes(pid_t tgid) {
271     jclass cls = mEnv->GetObjectClass(mCpuTimeInStateReader);
272     jmethodID mid = mEnv->GetMethodID(cls, "startTrackingProcessCpuTimes", "(I)Z");
273     if (mid == 0) {
274         ALOGE("Couldn't find the method startTrackingProcessCpuTimes");
275         return false;
276     }
277     return mEnv->CallBooleanMethod(mCpuTimeInStateReader, mid, tgid);
278 }
279 
startAggregatingTaskCpuTimes(pid_t pid,uint16_t aggregationKey)280 bool MockCpuTimeInStateReader::startAggregatingTaskCpuTimes(pid_t pid, uint16_t aggregationKey) {
281     jclass cls = mEnv->GetObjectClass(mCpuTimeInStateReader);
282     jmethodID mid = mEnv->GetMethodID(cls, "startAggregatingTaskCpuTimes", "(II)Z");
283     if (mid == 0) {
284         ALOGE("Couldn't find the method startAggregatingTaskCpuTimes");
285         return false;
286     }
287     return mEnv->CallBooleanMethod(mCpuTimeInStateReader, mid, pid, aggregationKey);
288 }
289 
290 std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
getAggregatedTaskCpuFreqTimes(pid_t pid,const std::vector<uint16_t> & aggregationKeys)291 MockCpuTimeInStateReader::getAggregatedTaskCpuFreqTimes(
292         pid_t pid, const std::vector<uint16_t> &aggregationKeys) {
293     jclass cls = mEnv->GetObjectClass(mCpuTimeInStateReader);
294     jmethodID mid =
295             mEnv->GetMethodID(cls, "getAggregatedTaskCpuFreqTimes", "(I)[Ljava/lang/String;");
296     if (mid == 0) {
297         ALOGE("Couldn't find the method getAggregatedTaskCpuFreqTimes");
298         return {};
299     }
300 
301     std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>> map;
302 
303     jobjectArray stringArray =
304             (jobjectArray)mEnv->CallObjectMethod(mCpuTimeInStateReader, mid, pid);
305     int size = mEnv->GetArrayLength(stringArray);
306     for (int i = 0; i < size; i++) {
307         ScopedUtfChars line(mEnv, (jstring)mEnv->GetObjectArrayElement(stringArray, i));
308         uint16_t aggregationKey;
309         std::vector<std::vector<uint64_t>> times;
310 
311         // Each string is formatted like this: "aggKey:t0_0 t0_1...:t1_0 t1_1..."
312         auto fields = android::base::Split(line.c_str(), ":");
313         android::base::ParseUint(fields[0], &aggregationKey);
314 
315         for (int j = 1; j < fields.size(); j++) {
316             auto numbers = android::base::Split(fields[j], " ");
317 
318             std::vector<uint64_t> chunk;
319             for (int k = 0; k < numbers.size(); k++) {
320                 uint64_t time;
321                 android::base::ParseUint(numbers[k], &time);
322                 chunk.emplace_back(time);
323             }
324             times.emplace_back(chunk);
325         }
326 
327         map.emplace(aggregationKey, times);
328     }
329 
330     return map;
331 }
332 
333 } // namespace android
334