| //===-- MemoryMonitor.cpp -------------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "lldb/Host/MemoryMonitor.h" |
| #include "lldb/Host/HostThread.h" |
| #include "lldb/Host/ThreadLauncher.h" |
| #include "lldb/Utility/LLDBLog.h" |
| #include "lldb/Utility/Log.h" |
| #include "llvm/ADT/ScopeExit.h" |
| #include "llvm/Support/Error.h" |
| #include <cstddef> |
| #include <cstdio> |
| #include <cstring> |
| |
| #if defined(__linux__) |
| #include "lldb/Host/posix/Support.h" |
| #include "llvm/Support/LineIterator.h" |
| #include <fcntl.h> |
| #include <poll.h> |
| #include <sys/eventfd.h> |
| #include <sys/poll.h> |
| #include <unistd.h> |
| #endif |
| |
| #if defined(_WIN32) |
| #include <atomic> |
| #include <windows.h> |
| #endif |
| |
| using namespace lldb_private; |
| |
| #if defined(__linux__) |
| class MemoryMonitorLinux : public MemoryMonitor { |
| public: |
| using MemoryMonitor::MemoryMonitor; |
| |
| explicit MemoryMonitorLinux(Callback callback) |
| : MemoryMonitor(std::move(callback)), |
| m_stop_fd(::eventfd(0, EFD_NONBLOCK)) {} |
| |
| ~MemoryMonitorLinux() { |
| if (m_memory_monitor_thread.IsJoinable()) |
| m_memory_monitor_thread.Join(nullptr); |
| if (m_stop_fd != -1) |
| ::close(m_stop_fd); |
| } |
| |
| void Start() override { |
| if (m_stop_fd < 0) { |
| LLDB_LOG_ERROR( |
| GetLog(LLDBLog::Host), |
| llvm::errorCodeToError(llvm::errnoAsErrorCode()), |
| "failed to create stop file descriptor for memory monitor: {0}"); |
| return; |
| } |
| |
| llvm::Expected<HostThread> memory_monitor_thread = |
| ThreadLauncher::LaunchThread("memory.monitor", |
| [this] { return MonitorThread(); }); |
| if (memory_monitor_thread) { |
| m_memory_monitor_thread = *memory_monitor_thread; |
| } else { |
| LLDB_LOG_ERROR(GetLog(LLDBLog::Host), memory_monitor_thread.takeError(), |
| "failed to launch host thread: {0}"); |
| } |
| } |
| |
| void Stop() override { |
| if (m_memory_monitor_thread.IsJoinable()) { |
| if (m_stop_fd != -1) |
| ::eventfd_write(m_stop_fd, 1); |
| m_memory_monitor_thread.Join(nullptr); |
| } |
| } |
| |
| private: |
| lldb::thread_result_t MonitorThread() { |
| constexpr size_t pressure_idx = 0; |
| constexpr size_t stop_idx = 1; |
| constexpr size_t fd_count = 2; |
| std::array<pollfd, fd_count> pfds{}; |
| |
| // Setup stop file descriptor. |
| pfds[stop_idx].fd = m_stop_fd; |
| pfds[stop_idx].events = POLLIN; |
| |
| // Setup pressure file descriptor. |
| pfds[pressure_idx].fd = |
| ::open("/proc/pressure/memory", O_RDWR | O_NONBLOCK); |
| if (pfds[pressure_idx].fd < 0) |
| return {}; |
| pfds[pressure_idx].events = POLLPRI; |
| |
| llvm::scope_exit cleanup([&]() { ::close(pfds[pressure_idx].fd); }); |
| |
| // Detect a 200ms stall in a 2 second time window. |
| constexpr llvm::StringRef trigger = "some 200000 2000000"; |
| if (::write(pfds[pressure_idx].fd, trigger.data(), trigger.size() + 1) < 0) |
| return {}; |
| |
| while (true) { |
| constexpr int timeout_infinite = -1; |
| const int n = ::poll(pfds.data(), pfds.size(), timeout_infinite); |
| if (n > 0) { |
| // Handle stop event. |
| if (pfds[stop_idx].revents & (POLLIN | POLLERR)) |
| return {}; |
| |
| const short pressure_revents = pfds[stop_idx].revents; |
| if (pressure_revents & POLLERR) |
| return {}; |
| if (pressure_revents & POLLPRI) { |
| if (const std::optional<bool> is_low_opt = IsLowMemory(); |
| is_low_opt && *is_low_opt) |
| m_callback(); |
| } |
| } |
| } |
| return {}; |
| } |
| |
| static std::optional<bool> IsLowMemory() { |
| llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> buffer_or_err = |
| getProcFile("meminfo"); |
| |
| if (!buffer_or_err) |
| return std::nullopt; |
| |
| uint64_t mem_total = 0; |
| uint64_t mem_available = 0; |
| const int radix = 10; |
| bool parse_error = false; |
| |
| for (llvm::line_iterator iter(**buffer_or_err, true); !iter.is_at_end(); |
| ++iter) { |
| llvm::StringRef line = *iter; |
| if (line.consume_front("MemTotal:")) |
| parse_error = line.ltrim().consumeInteger(radix, mem_total); |
| else if (line.consume_front("MemAvailable:")) |
| parse_error = line.ltrim().consumeInteger(radix, mem_available); |
| |
| if (parse_error) |
| return std::nullopt; |
| |
| if (mem_total && mem_available) |
| break; |
| } |
| |
| if (mem_total == 0) |
| return std::nullopt; |
| |
| if (mem_available == 0) // We are actually out of memory. |
| return true; |
| |
| const uint64_t approx_memory_percent = (mem_available * 100) / mem_total; |
| const uint64_t low_memory_percent = 20; |
| return approx_memory_percent < low_memory_percent; |
| } |
| |
| int m_stop_fd = -1; |
| HostThread m_memory_monitor_thread; |
| }; |
| #elif defined(_WIN32) |
| |
| class MemoryMonitorWindows : public MemoryMonitor { |
| public: |
| using MemoryMonitor::MemoryMonitor; |
| |
| lldb::thread_result_t MonitorThread() { |
| HANDLE low_memory_notification = |
| CreateMemoryResourceNotification(LowMemoryResourceNotification); |
| if (!low_memory_notification) |
| return {}; |
| |
| while (!m_done) { |
| if (WaitForSingleObject(low_memory_notification, g_timeout) == |
| WAIT_OBJECT_0) { |
| m_callback(); |
| } |
| } |
| return {}; |
| } |
| |
| void Start() override { |
| llvm::Expected<HostThread> memory_monitor_thread = |
| ThreadLauncher::LaunchThread("lldb.debugger.memory-monitor", |
| [this] { return MonitorThread(); }); |
| if (memory_monitor_thread) { |
| m_memory_monitor_thread = *memory_monitor_thread; |
| } else { |
| LLDB_LOG_ERROR(GetLog(LLDBLog::Host), memory_monitor_thread.takeError(), |
| "failed to launch host thread: {0}"); |
| } |
| } |
| |
| void Stop() override { |
| if (m_memory_monitor_thread.IsJoinable()) { |
| m_done = true; |
| m_memory_monitor_thread.Join(nullptr); |
| } |
| } |
| |
| private: |
| static constexpr uint32_t g_timeout = 1000; |
| std::atomic<bool> m_done = false; |
| HostThread m_memory_monitor_thread; |
| }; |
| #endif |
| |
| #if !defined(__APPLE__) |
| std::unique_ptr<MemoryMonitor> MemoryMonitor::Create(Callback callback) { |
| #if defined(__linux__) |
| return std::make_unique<MemoryMonitorLinux>(std::move(callback)); |
| #elif defined(_WIN32) |
| return std::make_unique<MemoryMonitorWindows>(std::move(callback)); |
| #else |
| return nullptr; |
| #endif |
| } |
| #endif |