• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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             .prog = MEM_EVENTS_LMKD_TRIGGER_VENDOR_LMK_KILL_TP,
128             .tpGroup = "android_vendor_lmk",
129             .tpEvent = "android_trigger_vendor_lmk_kill",
130             .event_type = MEM_EVENT_VENDOR_LMK_KILL
131         },
132         {
133             .prog = MEM_EVENTS_LMKD_CALCULATE_TOTALRESERVE_PAGES_TP,
134             .tpGroup = "kmem",
135             .tpEvent = "mm_calculate_totalreserve_pages",
136             .event_type = MEM_EVENT_UPDATE_ZONEINFO
137         },
138     },
139     // MemEventsTest
140     {
141         {
142             .prog = MEM_EVENTS_TEST_OOM_MARK_VICTIM_TP,
143             .tpGroup = "oom",
144             .tpEvent = "mark_victim",
145             .event_type = MEM_EVENT_OOM_KILL
146         },
147     },
148     // ... next service/client
149 };
150 // clang-format on
151 
findAttachment(mem_event_type_t event_type,MemEventClient client)152 static std::optional<MemBpfAttachment> findAttachment(mem_event_type_t event_type,
153                                                       MemEventClient client) {
154     auto it = std::find_if(attachments[client].begin(), attachments[client].end(),
155                            [event_type](const MemBpfAttachment memBpfAttch) {
156                                return memBpfAttch.event_type == event_type;
157                            });
158     if (it == attachments[client].end()) return std::nullopt;
159     return it[0];
160 }
161 
162 /**
163  * Helper function that determines if an event type is valid.
164  * We define "valid" as an actual event type that we can listen and register to.
165  *
166  * @param event_type memory event type to validate.
167  * @return true if it's less than `NR_MEM_EVENTS` and greater, or equal, to
168  * `MEM_EVENT_BASE`, false otherwise.
169  */
isValidEventType(mem_event_type_t event_type) const170 bool MemEventListener::isValidEventType(mem_event_type_t event_type) const {
171     return event_type < NR_MEM_EVENTS && event_type >= MEM_EVENT_BASE;
172 }
173 
174 // Public methods
175 
MemEventListener(MemEventClient client,bool attachTpForTests)176 MemEventListener::MemEventListener(MemEventClient client, bool attachTpForTests) {
177     if (client >= MemEventClient::NR_CLIENTS || client < MemEventClient::BASE) {
178         LOG(ERROR) << "memevent listener failed to initialize, invalid client: " << client;
179         std::abort();
180     }
181 
182     mClient = client;
183     mAttachTpForTests = attachTpForTests;
184     std::fill_n(mEventsRegistered, NR_MEM_EVENTS, false);
185     mNumEventsRegistered = 0;
186 
187     /*
188      * This flag allows for the MemoryPressureTest suite to hook into a BPF tracepoint
189      * and NOT allowing, this testing instance, to skip any skip internal calls.
190      * This flag is only allowed to be set for a testing instance, not for normal clients.
191      */
192     if (mClient != MemEventClient::TEST_CLIENT && attachTpForTests) {
193         LOG(ERROR) << "memevent listener failed to initialize, invalid configuration";
194         std::abort();
195     }
196 
197     memBpfRb = std::make_unique<MemBpfRingbuf>();
198     if (auto status = memBpfRb->Initialize(kClientRingBuffers[client].c_str()); !status.ok()) {
199         /*
200          * We allow for the listener to load gracefully, but we added safeguad
201          * throughout the public APIs to prevent the listener to do any actions.
202          */
203         memBpfRb.reset(nullptr);
204         if (isBpfRingBufferSupported) {
205             LOG(ERROR) << "memevent listener MemBpfRingbuf init failed: "
206                        << status.error().message();
207             /*
208              * Do not perform an `std::abort()`, there are some AMS test suites inadvertently
209              * initialize a memlistener to resolve test dependencies. We don't expect it
210              * to succeed since the test doesn't have the correct permissions.
211              */
212         } else {
213             LOG(ERROR) << "memevent listener failed to initialize, not supported kernel";
214         }
215     }
216 }
217 
~MemEventListener()218 MemEventListener::~MemEventListener() {
219     deregisterAllEvents();
220 }
221 
ok()222 bool MemEventListener::ok() {
223     return isBpfRingBufferSupported && memBpfRb;
224 }
225 
registerEvent(mem_event_type_t event_type)226 bool MemEventListener::registerEvent(mem_event_type_t event_type) {
227     if (!ok()) {
228         LOG(ERROR) << "memevent register failed, failure to initialize";
229         return false;
230     }
231     if (!isValidEventType(event_type)) {
232         LOG(ERROR) << "memevent register failed, received invalid event type";
233         return false;
234     }
235     if (mEventsRegistered[event_type]) {
236         // We are already registered to this event
237         return true;
238     }
239 
240     if (mClient == MemEventClient::TEST_CLIENT && !mAttachTpForTests) {
241         mEventsRegistered[event_type] = true;
242         mNumEventsRegistered++;
243         return true;
244     }
245 
246     const std::optional<MemBpfAttachment> maybeAttachment = findAttachment(event_type, mClient);
247     if (!maybeAttachment.has_value()) {
248         /*
249          * Not all clients have access to the same tracepoints, for example,
250          * AMS doesn't have a bpf prog for the direct reclaim start/end tracepoints.
251          */
252         LOG(ERROR) << "memevent register failed, client " << mClient
253                    << " doesn't support event: " << event_type;
254         return false;
255     }
256 
257     const auto attachment = maybeAttachment.value();
258     int bpf_prog_fd = retrieveProgram(attachment.prog.c_str());
259     if (bpf_prog_fd < 0) {
260         PLOG(ERROR) << "memevent failed to retrieve pinned program from: " << attachment.prog;
261         return false;
262     }
263 
264     /*
265      * Attach the bpf program to the tracepoint
266      *
267      * We get an errno `EEXIST` when a client attempts to register back to its events of interest.
268      * This occurs because the latest implementation of `bpf_detach_tracepoint` doesn't actually
269      * detach anything.
270      * https://github.com/iovisor/bcc/blob/7d350d90b638ddaf2c137a609b542e997597910a/src/cc/libbpf.c#L1495-L1501
271      */
272     if (bpf_attach_tracepoint(bpf_prog_fd, attachment.tpGroup.c_str(), attachment.tpEvent.c_str()) <
273                 0 &&
274         errno != EEXIST) {
275         PLOG(ERROR) << "memevent failed to attach bpf program to " << attachment.tpGroup << "/"
276                     << attachment.tpEvent << " tracepoint";
277         return false;
278     }
279 
280     mEventsRegistered[event_type] = true;
281     mNumEventsRegistered++;
282     return true;
283 }
284 
listen(int timeout_ms)285 bool MemEventListener::listen(int timeout_ms) {
286     if (!ok()) {
287         LOG(ERROR) << "memevent listen failed, failure to initialize";
288         return false;
289     }
290     if (mNumEventsRegistered == 0) {
291         LOG(ERROR) << "memevents listen failed, not registered to any events";
292         return false;
293     }
294 
295     return memBpfRb->wait(timeout_ms);
296 }
297 
deregisterEvent(mem_event_type_t event_type)298 bool MemEventListener::deregisterEvent(mem_event_type_t event_type) {
299     if (!ok()) {
300         LOG(ERROR) << "memevent failed to deregister, failure to initialize";
301         return false;
302     }
303     if (!isValidEventType(event_type)) {
304         LOG(ERROR) << "memevent failed to deregister, invalid event type";
305         return false;
306     }
307 
308     if (!mEventsRegistered[event_type]) return true;
309 
310     if (mClient == MemEventClient::TEST_CLIENT && !mAttachTpForTests) {
311         mEventsRegistered[event_type] = false;
312         mNumEventsRegistered--;
313         return true;
314     }
315 
316     const std::optional<MemBpfAttachment> maybeAttachment = findAttachment(event_type, mClient);
317     if (!maybeAttachment.has_value()) {
318         /*
319          * We never expect to get here since the listener wouldn't have been to register this
320          * `event_type` in the first place.
321          */
322         LOG(ERROR) << "memevent failed deregister event " << event_type
323                    << ", not tp attachment found";
324         return false;
325     }
326 
327     const auto attachment = maybeAttachment.value();
328     if (bpf_detach_tracepoint(attachment.tpGroup.c_str(), attachment.tpEvent.c_str()) < 0) {
329         PLOG(ERROR) << "memevent failed to deregister event " << event_type << " from bpf prog to "
330                     << attachment.tpGroup << "/" << attachment.tpEvent << " tracepoint";
331         return false;
332     }
333 
334     mEventsRegistered[event_type] = false;
335     mNumEventsRegistered--;
336     return true;
337 }
338 
deregisterAllEvents()339 void MemEventListener::deregisterAllEvents() {
340     if (!ok()) {
341         LOG(ERROR) << "memevent deregister all events failed, failure to initialize";
342         return;
343     }
344     if (mNumEventsRegistered == 0) return;
345     for (int i = 0; i < NR_MEM_EVENTS; i++) {
346         if (mEventsRegistered[i]) deregisterEvent(i);
347     }
348 }
349 
getMemEvents(std::vector<mem_event_t> & mem_events)350 bool MemEventListener::getMemEvents(std::vector<mem_event_t>& mem_events) {
351     // Ensure consuming from the BPF ring buffer is thread safe.
352     std::lock_guard<std::mutex> lock(mRingBufMutex);
353 
354     if (!ok()) {
355         LOG(ERROR) << "memevent failed getting memory events, failure to initialize";
356         return false;
357     }
358 
359     base::Result<int> ret = memBpfRb->ConsumeAll([&](const mem_event_t& mem_event) {
360         if (!isValidEventType(mem_event.type))
361             LOG(FATAL) << "Unexpected mem_event type: this should never happen: "
362                        << "there is likely data corruption due to memory ordering";
363 
364         if (mEventsRegistered[mem_event.type]) mem_events.emplace_back(mem_event);
365     });
366 
367     if (!ret.ok()) {
368         LOG(ERROR) << "memevent failed getting memory events: " << ret.error().message();
369         return false;
370     }
371 
372     return true;
373 }
374 
getRingBufferFd()375 int MemEventListener::getRingBufferFd() {
376     if (!ok()) {
377         LOG(ERROR) << "memevent failed getting ring-buffer fd, failure to initialize";
378         return -1;
379     }
380     return memBpfRb->getRingBufFd();
381 }
382 
383 }  // namespace memevents
384 }  // namespace bpf
385 }  // namespace android
386