1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
|
/*
* 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
|