blob: d0d136acc134ebe9971bc41dd305452a3a451195 [file] [log] [blame]
/*
* Copyright (C) 2011, 2012 Apple Inc. All Rights Reserved.
* Copyright (C) 2014 Raspberry Pi Foundation. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#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 {
// Disable memory event reception for a minimum of s_minimumHoldOffTime
// seconds after receiving an event. Don't let events fire any sooner than
// s_holdOffMultiplier times the last cleanup processing time. Effectively
// this is 1 / s_holdOffMultiplier percent of the time.
// These value seems reasonable and testing verifies that it throttles frequent
// low memory events, greatly reducing CPU usage.
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 on non-initial ASCII space.
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;
}
// FIXME: Current memcg does not provide any way for users to know how serious the memory pressure is.
// So we assume all notifications from memcg are critical for now. If memcg had better inferfaces
// to get a detailed memory pressure level in the future, we should update here accordingly.
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)
{
#ifdef __GLIBC__
ReliefLogger log("Run malloc_trim");
malloc_trim(0);
#endif
}
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); // KB
String token = nextToken(file);
while (!token.isEmpty()) {
if (token == "VmSize:") {
vmSize = nextToken(file).toInt() * KB;
break;
}
token = nextToken(file);
}
fclose(file);
return vmSize;
}
} // namespace WebCore
#endif // OS(LINUX)