//===-- 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 #include #include #if defined(__linux__) #include "lldb/Host/posix/Support.h" #include "llvm/Support/LineIterator.h" #include #include #include #include #include #endif #if defined(_WIN32) #include #include #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 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 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 is_low_opt = IsLowMemory(); is_low_opt && *is_low_opt) m_callback(); } } } return {}; } static std::optional IsLowMemory() { llvm::ErrorOr> 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 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 m_done = false; HostThread m_memory_monitor_thread; }; #endif #if !defined(__APPLE__) std::unique_ptr MemoryMonitor::Create(Callback callback) { #if defined(__linux__) return std::make_unique(std::move(callback)); #elif defined(_WIN32) return std::make_unique(std::move(callback)); #else return nullptr; #endif } #endif