LLDB mainline
MemoryMonitor.cpp
Go to the documentation of this file.
1//===-- MemoryMonitor.cpp -------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
13#include "lldb/Utility/Log.h"
14#include "llvm/ADT/ScopeExit.h"
15#include "llvm/Support/Error.h"
16#include <cstddef>
17#include <cstdio>
18#include <cstring>
19
20#if defined(__linux__)
22#include "llvm/Support/LineIterator.h"
23#include <fcntl.h>
24#include <poll.h>
25#include <sys/eventfd.h>
26#include <sys/poll.h>
27#include <unistd.h>
28#endif
29
30#if defined(_WIN32)
31#include <atomic>
32#include <windows.h>
33#endif
34
35using namespace lldb_private;
36
37#if defined(__linux__)
38class MemoryMonitorLinux : public MemoryMonitor {
39public:
41
42 explicit MemoryMonitorLinux(Callback callback)
43 : MemoryMonitor(std::move(callback)),
44 m_stop_fd(::eventfd(0, EFD_NONBLOCK)) {}
45
46 ~MemoryMonitorLinux() {
47 if (m_memory_monitor_thread.IsJoinable())
48 m_memory_monitor_thread.Join(nullptr);
49 if (m_stop_fd != -1)
50 ::close(m_stop_fd);
51 }
52
53 void Start() override {
54 if (m_stop_fd < 0) {
56 GetLog(LLDBLog::Host),
57 llvm::errorCodeToError(llvm::errnoAsErrorCode()),
58 "failed to create stop file descriptor for memory monitor: {0}");
59 return;
60 }
61
62 llvm::Expected<HostThread> memory_monitor_thread =
63 ThreadLauncher::LaunchThread("memory.monitor",
64 [this] { return MonitorThread(); });
65 if (memory_monitor_thread) {
66 m_memory_monitor_thread = *memory_monitor_thread;
67 } else {
68 LLDB_LOG_ERROR(GetLog(LLDBLog::Host), memory_monitor_thread.takeError(),
69 "failed to launch host thread: {0}");
70 }
71 }
72
73 void Stop() override {
74 if (m_memory_monitor_thread.IsJoinable()) {
75 if (m_stop_fd != -1)
76 ::eventfd_write(m_stop_fd, 1);
77 m_memory_monitor_thread.Join(nullptr);
78 }
79 }
80
81private:
83 constexpr size_t pressure_idx = 0;
84 constexpr size_t stop_idx = 1;
85 constexpr size_t fd_count = 2;
86 std::array<pollfd, fd_count> pfds{};
87
88 // Setup stop file descriptor.
89 pfds[stop_idx].fd = m_stop_fd;
90 pfds[stop_idx].events = POLLIN;
91
92 // Setup pressure file descriptor.
93 pfds[pressure_idx].fd =
94 ::open("/proc/pressure/memory", O_RDWR | O_NONBLOCK);
95 if (pfds[pressure_idx].fd < 0)
96 return {};
97 pfds[pressure_idx].events = POLLPRI;
98
99 llvm::scope_exit cleanup([&]() { ::close(pfds[pressure_idx].fd); });
100
101 // Detect a 200ms stall in a 2 second time window.
102 constexpr llvm::StringRef trigger = "some 200000 2000000";
103 if (::write(pfds[pressure_idx].fd, trigger.data(), trigger.size() + 1) < 0)
104 return {};
105
106 while (true) {
107 constexpr int timeout_infinite = -1;
108 const int n = ::poll(pfds.data(), pfds.size(), timeout_infinite);
109 if (n > 0) {
110 // Handle stop event.
111 if (pfds[stop_idx].revents & (POLLIN | POLLERR))
112 return {};
113
114 const short pressure_revents = pfds[stop_idx].revents;
115 if (pressure_revents & POLLERR)
116 return {};
117 if (pressure_revents & POLLPRI) {
118 if (const std::optional<bool> is_low_opt = IsLowMemory();
119 is_low_opt && *is_low_opt)
120 m_callback();
121 }
122 }
123 }
124 return {};
125 }
126
127 static std::optional<bool> IsLowMemory() {
128 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> buffer_or_err =
129 getProcFile("meminfo");
130
131 if (!buffer_or_err)
132 return std::nullopt;
133
134 uint64_t mem_total = 0;
135 uint64_t mem_available = 0;
136 const int radix = 10;
137 bool parse_error = false;
138
139 for (llvm::line_iterator iter(**buffer_or_err, true); !iter.is_at_end();
140 ++iter) {
141 llvm::StringRef line = *iter;
142 if (line.consume_front("MemTotal:"))
143 parse_error = line.ltrim().consumeInteger(radix, mem_total);
144 else if (line.consume_front("MemAvailable:"))
145 parse_error = line.ltrim().consumeInteger(radix, mem_available);
146
147 if (parse_error)
148 return std::nullopt;
149
150 if (mem_total && mem_available)
151 break;
152 }
153
154 if (mem_total == 0)
155 return std::nullopt;
156
157 if (mem_available == 0) // We are actually out of memory.
158 return true;
159
160 const uint64_t approx_memory_percent = (mem_available * 100) / mem_total;
161 const uint64_t low_memory_percent = 20;
162 return approx_memory_percent < low_memory_percent;
163 }
164
165 int m_stop_fd = -1;
166 HostThread m_memory_monitor_thread;
167};
168#elif defined(_WIN32)
169
170class MemoryMonitorWindows : public MemoryMonitor {
171public:
173
175 HANDLE low_memory_notification =
176 CreateMemoryResourceNotification(LowMemoryResourceNotification);
177 if (!low_memory_notification)
178 return {};
179
180 while (!m_done) {
181 if (WaitForSingleObject(low_memory_notification, g_timeout) ==
182 WAIT_OBJECT_0) {
183 m_callback();
184 }
185 }
186 return {};
187 }
188
189 void Start() override {
190 llvm::Expected<HostThread> memory_monitor_thread =
191 ThreadLauncher::LaunchThread("lldb.debugger.memory-monitor",
192 [this] { return MonitorThread(); });
193 if (memory_monitor_thread) {
194 m_memory_monitor_thread = *memory_monitor_thread;
195 } else {
196 LLDB_LOG_ERROR(GetLog(LLDBLog::Host), memory_monitor_thread.takeError(),
197 "failed to launch host thread: {0}");
198 }
199 }
200
201 void Stop() override {
202 if (m_memory_monitor_thread.IsJoinable()) {
203 m_done = true;
204 m_memory_monitor_thread.Join(nullptr);
205 }
206 }
207
208private:
209 static constexpr uint32_t g_timeout = 1000;
210 std::atomic<bool> m_done = false;
211 HostThread m_memory_monitor_thread;
212};
213#endif
214
215#if !defined(__APPLE__)
216std::unique_ptr<MemoryMonitor> MemoryMonitor::Create(Callback callback) {
217#if defined(__linux__)
218 return std::make_unique<MemoryMonitorLinux>(std::move(callback));
219#elif defined(_WIN32)
220 return std::make_unique<MemoryMonitorWindows>(std::move(callback));
221#else
222 return nullptr;
223#endif
224}
225#endif
static lldb::thread_result_t MonitorThread(const Host::MonitorChildProcessCallback &callback, HANDLE process_handle)
#define LLDB_LOG_ERROR(log, error,...)
Definition Log.h:392
void * HANDLE
MemoryMonitor(Callback callback)
static std::unique_ptr< MemoryMonitor > Create(Callback callback)
std::function< void()> Callback
static llvm::Expected< HostThread > LaunchThread(llvm::StringRef name, std::function< lldb::thread_result_t()> thread_function, size_t min_stack_byte_size=0)
A class that represents a running process on the host machine.
Log * GetLog(Cat mask)
Retrieve the Log object for the channel associated with the given log enum.
Definition Log.h:332
llvm::ErrorOr< std::unique_ptr< llvm::MemoryBuffer > > getProcFile(::pid_t pid, ::pid_t tid, const llvm::Twine &file)
void * thread_result_t
Definition lldb-types.h:62
#define O_NONBLOCK