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