MemoryPressureHandlerLinux.cpp [plain text]
#include "config.h"
#include "MemoryPressureHandler.h"
#if OS(LINUX)
#include "Logging.h"
#include <fcntl.h>
#include <malloc.h>
#include <sys/eventfd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <wtf/CurrentTime.h>
#include <wtf/MainThread.h>
#include <wtf/text/WTFString.h>
namespace WebCore {
static const unsigned s_minimumHoldOffTime = 5;
static const unsigned s_holdOffMultiplier = 20;
static const char* s_cgroupMemoryPressureLevel = "/sys/fs/cgroup/memory/memory.pressure_level";
static const char* s_cgroupEventControl = "/sys/fs/cgroup/memory/cgroup.event_control";
static const char* s_processStatus = "/proc/self/status";
static inline String nextToken(FILE* file)
{
if (!file)
return String();
static const unsigned bufferSize = 128;
char buffer[bufferSize] = {0, };
unsigned index = 0;
while (index < bufferSize) {
int ch = fgetc(file);
if (ch == EOF || (isASCIISpace(ch) && index)) break;
if (!isASCIISpace(ch)) {
buffer[index] = ch;
index++;
}
}
return String(buffer);
}
void MemoryPressureHandler::waitForMemoryPressureEvent(void*)
{
ASSERT(!isMainThread());
int eventFD = MemoryPressureHandler::singleton().m_eventFD;
if (!eventFD) {
LOG(MemoryPressure, "Invalidate eventfd.");
return;
}
uint64_t buffer;
if (read(eventFD, &buffer, sizeof(buffer)) <= 0) {
LOG(MemoryPressure, "Failed to read eventfd.");
return;
}
bool critical = true;
if (ReliefLogger::loggingEnabled())
LOG(MemoryPressure, "Got memory pressure notification (%s)", critical ? "critical" : "non-critical");
MemoryPressureHandler::singleton().setUnderMemoryPressure(critical);
callOnMainThread([critical] {
MemoryPressureHandler::singleton().respondToMemoryPressure(critical ? Critical::Yes : Critical::No);
});
}
inline void MemoryPressureHandler::logErrorAndCloseFDs(const char* log)
{
if (log)
LOG(MemoryPressure, "%s, error : %m", log);
if (m_eventFD) {
close(m_eventFD);
m_eventFD = 0;
}
if (m_pressureLevelFD) {
close(m_pressureLevelFD);
m_pressureLevelFD = 0;
}
}
void MemoryPressureHandler::install()
{
if (m_installed)
return;
m_eventFD = eventfd(0, EFD_CLOEXEC);
if (m_eventFD == -1) {
LOG(MemoryPressure, "eventfd() failed: %m");
return;
}
m_pressureLevelFD = open(s_cgroupMemoryPressureLevel, O_CLOEXEC | O_RDONLY);
if (m_pressureLevelFD == -1) {
logErrorAndCloseFDs("Failed to open memory.pressure_level");
return;
}
int fd = open(s_cgroupEventControl, O_CLOEXEC | O_WRONLY);
if (fd == -1) {
logErrorAndCloseFDs("Failed to open cgroup.event_control");
return;
}
char line[128] = {0, };
if (snprintf(line, sizeof(line), "%d %d low", m_eventFD, m_pressureLevelFD) < 0
|| write(fd, line, strlen(line) + 1) < 0) {
logErrorAndCloseFDs("Failed to write cgroup.event_control");
close(fd);
return;
}
close(fd);
m_threadID = createThread(waitForMemoryPressureEvent, this, "WebCore: MemoryPressureHandler");
if (!m_threadID) {
logErrorAndCloseFDs("Failed to create a thread for MemoryPressureHandler");
return;
}
if (ReliefLogger::loggingEnabled() && isUnderMemoryPressure())
LOG(MemoryPressure, "System is no longer under memory pressure.");
setUnderMemoryPressure(false);
m_installed = true;
}
void MemoryPressureHandler::uninstall()
{
if (!m_installed)
return;
if (m_threadID) {
detachThread(m_threadID);
m_threadID = 0;
}
logErrorAndCloseFDs(nullptr);
m_installed = false;
}
void MemoryPressureHandler::holdOffTimerFired()
{
install();
}
void MemoryPressureHandler::holdOff(unsigned seconds)
{
m_holdOffTimer.startOneShot(seconds);
}
void MemoryPressureHandler::respondToMemoryPressure(Critical critical, Synchronous synchronous)
{
uninstall();
double startTime = monotonicallyIncreasingTime();
m_lowMemoryHandler(critical, synchronous);
unsigned holdOffTime = (monotonicallyIncreasingTime() - startTime) * s_holdOffMultiplier;
holdOff(std::max(holdOffTime, s_minimumHoldOffTime));
}
void MemoryPressureHandler::platformReleaseMemory(Critical)
{
ReliefLogger log("Run malloc_trim");
malloc_trim(0);
}
void MemoryPressureHandler::ReliefLogger::platformLog()
{
size_t currentMemory = platformMemoryUsage();
if (currentMemory == static_cast<size_t>(-1) || m_initialMemory == static_cast<size_t>(-1)) {
LOG(MemoryPressure, "%s (Unable to get dirty memory information for process)", m_logString);
return;
}
ssize_t memoryDiff = currentMemory - m_initialMemory;
if (memoryDiff < 0)
LOG(MemoryPressure, "Pressure relief: %s: -dirty %lu bytes (from %lu to %lu)", m_logString, static_cast<unsigned long>(memoryDiff * -1), static_cast<unsigned long>(m_initialMemory), static_cast<unsigned long>(currentMemory));
else if (memoryDiff > 0)
LOG(MemoryPressure, "Pressure relief: %s: +dirty %lu bytes (from %lu to %lu)", m_logString, static_cast<unsigned long>(memoryDiff), static_cast<unsigned long>(m_initialMemory), static_cast<unsigned long>(currentMemory));
else
LOG(MemoryPressure, "Pressure relief: %s: =dirty (at %lu bytes)", m_logString, static_cast<unsigned long>(currentMemory));
}
size_t MemoryPressureHandler::ReliefLogger::platformMemoryUsage()
{
FILE* file = fopen(s_processStatus, "r");
if (!file)
return static_cast<size_t>(-1);
size_t vmSize = static_cast<size_t>(-1); String token = nextToken(file);
while (!token.isEmpty()) {
if (token == "VmSize:") {
vmSize = nextToken(file).toInt() * KB;
break;
}
token = nextToken(file);
}
fclose(file);
return vmSize;
}
}
#endif // OS(LINUX)