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