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 #include <bpf/BpfMap.h>
18 #include <bpf/BpfRingbuf.h>
19 #include <bpf/WaitForProgsLoaded.h>
20 #include <fcntl.h>
21 #include <inttypes.h>
22 #include <libbpf.h>
23 #include <sys/epoll.h>
24 #include <unistd.h>
25
26 #include <algorithm>
27 #include <cstdio>
28 #include <functional>
29 #include <mutex>
30 #include <optional>
31 #include <sstream>
32 #include <string>
33
34 #include <android-base/file.h>
35 #include <android-base/logging.h>
36 #include <android-base/result.h>
37
38 #include <memevents/memevents.h>
39
40 namespace android {
41 namespace bpf {
42 namespace memevents {
43
44 static const std::string kClientRingBuffers[MemEventClient::NR_CLIENTS] = {
45 MEM_EVENTS_AMS_RB, MEM_EVENTS_LMKD_RB, MEM_EVENTS_TEST_RB};
46
47 static const bool isBpfRingBufferSupported = isAtLeastKernelVersion(5, 8, 0);
48
49 class MemBpfRingbuf : public BpfRingbufBase {
50 public:
51 using EventCallback = std::function<void(const mem_event_t&)>;
52
53 /*
54 * Non-initializing constructor, requires calling `Initialize` once.
55 * This allows us to handle gracefully when we encounter an init
56 * error, instead of using a full-constructions that aborts on error.
57 */
MemBpfRingbuf()58 MemBpfRingbuf() : BpfRingbufBase(sizeof(mem_event_t)) {}
59
60 /*
61 * Initialize the base ringbuffer components. Must be called exactly once.
62 */
Initialize(const char * path)63 base::Result<void> Initialize(const char* path) { return Init(path); }
64
65 /*
66 * Consumes all `mem_event_t` messages from the ring buffer, passing them
67 * to the callback.
68 */
ConsumeAll(const EventCallback & callback)69 base::Result<int> ConsumeAll(const EventCallback& callback) {
70 return BpfRingbufBase::ConsumeAll([&](const void* mem_event) {
71 callback(*reinterpret_cast<const mem_event_t*>(mem_event));
72 });
73 }
74
75 /*
76 * Expose ring buffer file descriptor for polling purposes, not intended for
77 * consume directly. To consume use `ConsumeAll()`.
78 */
getRingBufFd()79 int getRingBufFd() { return mRingFd.get(); }
80 };
81
82 struct MemBpfAttachment {
83 const std::string prog;
84 const std::string tpGroup;
85 const std::string tpEvent;
86 const mem_event_type_t event_type;
87 };
88
89 // clang-format off
90 static const std::vector<std::vector<struct MemBpfAttachment>> attachments = {
91 // AMS
92 {
93 {
94 .prog = MEM_EVENTS_AMS_OOM_MARK_VICTIM_TP,
95 .tpGroup = "oom",
96 .tpEvent = "mark_victim",
97 .event_type = MEM_EVENT_OOM_KILL
98 },
99 },
100 // LMKD
101 {
102 {
103 .prog = MEM_EVENTS_LMKD_VMSCAN_DR_BEGIN_TP,
104 .tpGroup = "vmscan",
105 .tpEvent = "mm_vmscan_direct_reclaim_begin",
106 .event_type = MEM_EVENT_DIRECT_RECLAIM_BEGIN
107 },
108 {
109 .prog = MEM_EVENTS_LMKD_VMSCAN_DR_END_TP,
110 .tpGroup = "vmscan",
111 .tpEvent = "mm_vmscan_direct_reclaim_end",
112 .event_type = MEM_EVENT_DIRECT_RECLAIM_END
113 },
114 {
115 .prog = MEM_EVENTS_LMKD_VMSCAN_KSWAPD_WAKE_TP,
116 .tpGroup = "vmscan",
117 .tpEvent = "mm_vmscan_kswapd_wake",
118 .event_type = MEM_EVENT_KSWAPD_WAKE
119 },
120 {
121 .prog = MEM_EVENTS_LMKD_VMSCAN_KSWAPD_SLEEP_TP,
122 .tpGroup = "vmscan",
123 .tpEvent = "mm_vmscan_kswapd_sleep",
124 .event_type = MEM_EVENT_KSWAPD_SLEEP
125 },
126 },
127 // MemEventsTest
128 {
129 {
130 .prog = MEM_EVENTS_TEST_OOM_MARK_VICTIM_TP,
131 .tpGroup = "oom",
132 .tpEvent = "mark_victim",
133 .event_type = MEM_EVENT_OOM_KILL
134 },
135 },
136 // ... next service/client
137 };
138 // clang-format on
139
findAttachment(mem_event_type_t event_type,MemEventClient client)140 static std::optional<MemBpfAttachment> findAttachment(mem_event_type_t event_type,
141 MemEventClient client) {
142 auto it = std::find_if(attachments[client].begin(), attachments[client].end(),
143 [event_type](const MemBpfAttachment memBpfAttch) {
144 return memBpfAttch.event_type == event_type;
145 });
146 if (it == attachments[client].end()) return std::nullopt;
147 return it[0];
148 }
149
150 /**
151 * Helper function that determines if an event type is valid.
152 * We define "valid" as an actual event type that we can listen and register to.
153 *
154 * @param event_type memory event type to validate.
155 * @return true if it's less than `NR_MEM_EVENTS` and greater, or equal, to
156 * `MEM_EVENT_BASE`, false otherwise.
157 */
isValidEventType(mem_event_type_t event_type) const158 bool MemEventListener::isValidEventType(mem_event_type_t event_type) const {
159 return event_type < NR_MEM_EVENTS && event_type >= MEM_EVENT_BASE;
160 }
161
162 // Public methods
163
MemEventListener(MemEventClient client,bool attachTpForTests)164 MemEventListener::MemEventListener(MemEventClient client, bool attachTpForTests) {
165 if (client >= MemEventClient::NR_CLIENTS || client < MemEventClient::BASE) {
166 LOG(ERROR) << "memevent listener failed to initialize, invalid client: " << client;
167 std::abort();
168 }
169
170 mClient = client;
171 mAttachTpForTests = attachTpForTests;
172 std::fill_n(mEventsRegistered, NR_MEM_EVENTS, false);
173 mNumEventsRegistered = 0;
174
175 /*
176 * This flag allows for the MemoryPressureTest suite to hook into a BPF tracepoint
177 * and NOT allowing, this testing instance, to skip any skip internal calls.
178 * This flag is only allowed to be set for a testing instance, not for normal clients.
179 */
180 if (mClient != MemEventClient::TEST_CLIENT && attachTpForTests) {
181 LOG(ERROR) << "memevent listener failed to initialize, invalid configuration";
182 std::abort();
183 }
184
185 memBpfRb = std::make_unique<MemBpfRingbuf>();
186 if (auto status = memBpfRb->Initialize(kClientRingBuffers[client].c_str()); !status.ok()) {
187 /*
188 * We allow for the listener to load gracefully, but we added safeguad
189 * throughout the public APIs to prevent the listener to do any actions.
190 */
191 memBpfRb.reset(nullptr);
192 if (isBpfRingBufferSupported) {
193 LOG(ERROR) << "memevent listener MemBpfRingbuf init failed: "
194 << status.error().message();
195 /*
196 * Do not perform an `std::abort()`, there are some AMS test suites inadvertently
197 * initialize a memlistener to resolve test dependencies. We don't expect it
198 * to succeed since the test doesn't have the correct permissions.
199 */
200 } else {
201 LOG(ERROR) << "memevent listener failed to initialize, not supported kernel";
202 }
203 }
204 }
205
~MemEventListener()206 MemEventListener::~MemEventListener() {
207 deregisterAllEvents();
208 }
209
ok()210 bool MemEventListener::ok() {
211 return isBpfRingBufferSupported && memBpfRb;
212 }
213
registerEvent(mem_event_type_t event_type)214 bool MemEventListener::registerEvent(mem_event_type_t event_type) {
215 if (!ok()) {
216 LOG(ERROR) << "memevent register failed, failure to initialize";
217 return false;
218 }
219 if (!isValidEventType(event_type)) {
220 LOG(ERROR) << "memevent register failed, received invalid event type";
221 return false;
222 }
223 if (mEventsRegistered[event_type]) {
224 // We are already registered to this event
225 return true;
226 }
227
228 if (mClient == MemEventClient::TEST_CLIENT && !mAttachTpForTests) {
229 mEventsRegistered[event_type] = true;
230 mNumEventsRegistered++;
231 return true;
232 }
233
234 const std::optional<MemBpfAttachment> maybeAttachment = findAttachment(event_type, mClient);
235 if (!maybeAttachment.has_value()) {
236 /*
237 * Not all clients have access to the same tracepoints, for example,
238 * AMS doesn't have a bpf prog for the direct reclaim start/end tracepoints.
239 */
240 LOG(ERROR) << "memevent register failed, client " << mClient
241 << " doesn't support event: " << event_type;
242 return false;
243 }
244
245 const auto attachment = maybeAttachment.value();
246 int bpf_prog_fd = retrieveProgram(attachment.prog.c_str());
247 if (bpf_prog_fd < 0) {
248 PLOG(ERROR) << "memevent failed to retrieve pinned program from: " << attachment.prog;
249 return false;
250 }
251
252 /*
253 * Attach the bpf program to the tracepoint
254 *
255 * We get an errno `EEXIST` when a client attempts to register back to its events of interest.
256 * This occurs because the latest implementation of `bpf_detach_tracepoint` doesn't actually
257 * detach anything.
258 * https://github.com/iovisor/bcc/blob/7d350d90b638ddaf2c137a609b542e997597910a/src/cc/libbpf.c#L1495-L1501
259 */
260 if (bpf_attach_tracepoint(bpf_prog_fd, attachment.tpGroup.c_str(), attachment.tpEvent.c_str()) <
261 0 &&
262 errno != EEXIST) {
263 PLOG(ERROR) << "memevent failed to attach bpf program to " << attachment.tpGroup << "/"
264 << attachment.tpEvent << " tracepoint";
265 return false;
266 }
267
268 mEventsRegistered[event_type] = true;
269 mNumEventsRegistered++;
270 return true;
271 }
272
listen(int timeout_ms)273 bool MemEventListener::listen(int timeout_ms) {
274 if (!ok()) {
275 LOG(ERROR) << "memevent listen failed, failure to initialize";
276 return false;
277 }
278 if (mNumEventsRegistered == 0) {
279 LOG(ERROR) << "memevents listen failed, not registered to any events";
280 return false;
281 }
282
283 return memBpfRb->wait(timeout_ms);
284 }
285
deregisterEvent(mem_event_type_t event_type)286 bool MemEventListener::deregisterEvent(mem_event_type_t event_type) {
287 if (!ok()) {
288 LOG(ERROR) << "memevent failed to deregister, failure to initialize";
289 return false;
290 }
291 if (!isValidEventType(event_type)) {
292 LOG(ERROR) << "memevent failed to deregister, invalid event type";
293 return false;
294 }
295
296 if (!mEventsRegistered[event_type]) return true;
297
298 if (mClient == MemEventClient::TEST_CLIENT && !mAttachTpForTests) {
299 mEventsRegistered[event_type] = false;
300 mNumEventsRegistered--;
301 return true;
302 }
303
304 const std::optional<MemBpfAttachment> maybeAttachment = findAttachment(event_type, mClient);
305 if (!maybeAttachment.has_value()) {
306 /*
307 * We never expect to get here since the listener wouldn't have been to register this
308 * `event_type` in the first place.
309 */
310 LOG(ERROR) << "memevent failed deregister event " << event_type
311 << ", not tp attachment found";
312 return false;
313 }
314
315 const auto attachment = maybeAttachment.value();
316 if (bpf_detach_tracepoint(attachment.tpGroup.c_str(), attachment.tpEvent.c_str()) < 0) {
317 PLOG(ERROR) << "memevent failed to deregister event " << event_type << " from bpf prog to "
318 << attachment.tpGroup << "/" << attachment.tpEvent << " tracepoint";
319 return false;
320 }
321
322 mEventsRegistered[event_type] = false;
323 mNumEventsRegistered--;
324 return true;
325 }
326
deregisterAllEvents()327 void MemEventListener::deregisterAllEvents() {
328 if (!ok()) {
329 LOG(ERROR) << "memevent deregister all events failed, failure to initialize";
330 return;
331 }
332 if (mNumEventsRegistered == 0) return;
333 for (int i = 0; i < NR_MEM_EVENTS; i++) {
334 if (mEventsRegistered[i]) deregisterEvent(i);
335 }
336 }
337
getMemEvents(std::vector<mem_event_t> & mem_events)338 bool MemEventListener::getMemEvents(std::vector<mem_event_t>& mem_events) {
339 // Ensure consuming from the BPF ring buffer is thread safe.
340 std::lock_guard<std::mutex> lock(mRingBufMutex);
341
342 if (!ok()) {
343 LOG(ERROR) << "memevent failed getting memory events, failure to initialize";
344 return false;
345 }
346
347 base::Result<int> ret = memBpfRb->ConsumeAll([&](const mem_event_t& mem_event) {
348 if (!isValidEventType(mem_event.type))
349 LOG(FATAL) << "Unexpected mem_event type: this should never happen: "
350 << "there is likely data corruption due to memory ordering";
351
352 if (mEventsRegistered[mem_event.type]) mem_events.emplace_back(mem_event);
353 });
354
355 if (!ret.ok()) {
356 LOG(ERROR) << "memevent failed getting memory events: " << ret.error().message();
357 return false;
358 }
359
360 return true;
361 }
362
getRingBufferFd()363 int MemEventListener::getRingBufferFd() {
364 if (!ok()) {
365 LOG(ERROR) << "memevent failed getting ring-buffer fd, failure to initialize";
366 return -1;
367 }
368 return memBpfRb->getRingBufFd();
369 }
370
371 } // namespace memevents
372 } // namespace bpf
373 } // namespace android
374