Only call the callback when we are actually low on memory. Increase the threshold to 200ms in a 2 second interval to match systemd's default see [DefaultMemoryPressureWatch](https://www.freedesktop.org/software/systemd/man/latest/systemd-system.conf.html#DefaultMemoryPressureWatch=).
226 lines
6.3 KiB
C++
226 lines
6.3 KiB
C++
//===-- 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
|