Implement MemoryPressureHandler for Linux system
authorchangseok.oh@collabora.com <changseok.oh@collabora.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 12 Dec 2014 09:21:15 +0000 (09:21 +0000)
committerchangseok.oh@collabora.com <changseok.oh@collabora.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 12 Dec 2014 09:21:15 +0000 (09:21 +0000)
https://bugs.webkit.org/show_bug.cgi?id=123532

Reviewed by Sergio Villar Senin.

This is an initial implementation to support MemoryPressureHandler for linux system.
The patch is based on Tomeu's last patch and improved on top of it.
Most of current linux distributions support cgroup, so that we use the memory.pressure_level
mechanism of cgroup to get notifications when an application reaches the 'low' memory
pressure level.

No new tests since no engine behavior changed.

* PlatformEfl.cmake:
* PlatformGTK.cmake:
* platform/MemoryPressureHandler.cpp:
(WebCore::MemoryPressureHandler::MemoryPressureHandler):
* platform/MemoryPressureHandler.h:
* platform/linux/MemoryPressureHandlerLinux.cpp: Added.
(WebCore::nextToken):
(WebCore::MemoryPressureHandler::~MemoryPressureHandler):
(WebCore::MemoryPressureHandler::waitForMemoryPressureEvent): run in a seperated thread
to listen 'low' level event.
(WebCore::MemoryPressureHandler::logErrorAndCloseFDs):
(WebCore::MemoryPressureHandler::install):
(WebCore::MemoryPressureHandler::uninstall):
(WebCore::MemoryPressureHandler::holdOffTimerFired):
(WebCore::MemoryPressureHandler::holdOff):
(WebCore::MemoryPressureHandler::respondToMemoryPressure):
(WebCore::MemoryPressureHandler::platformReleaseMemory):
(WebCore::MemoryPressureHandler::ReliefLogger::platformLog):
(WebCore::MemoryPressureHandler::ReliefLogger::platformMemoryUsage): read /proc/self/status
to get VM amount used by current process.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@177216 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/PlatformEfl.cmake
Source/WebCore/PlatformGTK.cmake
Source/WebCore/platform/MemoryPressureHandler.cpp
Source/WebCore/platform/MemoryPressureHandler.h
Source/WebCore/platform/linux/MemoryPressureHandlerLinux.cpp [new file with mode: 0644]

index 2df38c5..914dcab 100644 (file)
@@ -1,3 +1,39 @@
+2014-12-12  ChangSeok Oh  <changseok.oh@collabora.com>
+
+        Implement MemoryPressureHandler for Linux system
+        https://bugs.webkit.org/show_bug.cgi?id=123532
+
+        Reviewed by Sergio Villar Senin.
+
+        This is an initial implementation to support MemoryPressureHandler for linux system.
+        The patch is based on Tomeu's last patch and improved on top of it.
+        Most of current linux distributions support cgroup, so that we use the memory.pressure_level
+        mechanism of cgroup to get notifications when an application reaches the 'low' memory
+        pressure level.
+
+        No new tests since no engine behavior changed.
+
+        * PlatformEfl.cmake:
+        * PlatformGTK.cmake:
+        * platform/MemoryPressureHandler.cpp:
+        (WebCore::MemoryPressureHandler::MemoryPressureHandler):
+        * platform/MemoryPressureHandler.h:
+        * platform/linux/MemoryPressureHandlerLinux.cpp: Added.
+        (WebCore::nextToken):
+        (WebCore::MemoryPressureHandler::~MemoryPressureHandler):
+        (WebCore::MemoryPressureHandler::waitForMemoryPressureEvent): run in a seperated thread
+        to listen 'low' level event.
+        (WebCore::MemoryPressureHandler::logErrorAndCloseFDs):
+        (WebCore::MemoryPressureHandler::install):
+        (WebCore::MemoryPressureHandler::uninstall):
+        (WebCore::MemoryPressureHandler::holdOffTimerFired):
+        (WebCore::MemoryPressureHandler::holdOff):
+        (WebCore::MemoryPressureHandler::respondToMemoryPressure):
+        (WebCore::MemoryPressureHandler::platformReleaseMemory):
+        (WebCore::MemoryPressureHandler::ReliefLogger::platformLog):
+        (WebCore::MemoryPressureHandler::ReliefLogger::platformMemoryUsage): read /proc/self/status
+        to get VM amount used by current process.
+
 2014-12-12  Simon Fraser  <simon.fraser@apple.com>
 
         Layer borders on contentsLayers don't correctly toggle with the rest of the borders
index 41ff5f2..1bbed0f 100644 (file)
@@ -212,6 +212,7 @@ list(APPEND WebCore_SOURCES
     platform/image-decoders/webp/WEBPImageDecoder.cpp
 
     platform/linux/GamepadDeviceLinux.cpp
+    platform/linux/MemoryPressureHandlerLinux.cpp
 
     platform/mediastream/gstreamer/MediaStreamCenterGStreamer.cpp
 
index 226f8c5..aa4bc9c 100644 (file)
@@ -156,6 +156,7 @@ list(APPEND WebCore_SOURCES
     platform/image-decoders/webp/WEBPImageDecoder.cpp
 
     platform/linux/GamepadDeviceLinux.cpp
+    platform/linux/MemoryPressureHandlerLinux.cpp
 
     platform/mediastream/gstreamer/MediaStreamCenterGStreamer.cpp
 
index e66c13d..1812dec 100644 (file)
@@ -64,6 +64,11 @@ MemoryPressureHandler::MemoryPressureHandler()
     , m_clearPressureOnMemoryRelease(true)
     , m_releaseMemoryBlock(0)
     , m_observer(0)
+#elif OS(LINUX)
+    , m_eventFD(0)
+    , m_pressureLevelFD(0)
+    , m_threadID(0)
+    , m_holdOffTimer(*this, &MemoryPressureHandler::holdOffTimerFired)
 #endif
 {
 }
@@ -143,7 +148,7 @@ void MemoryPressureHandler::releaseMemory(bool critical)
     }
 }
 
-#if !PLATFORM(COCOA)
+#if !PLATFORM(COCOA) && !OS(LINUX)
 void MemoryPressureHandler::install() { }
 void MemoryPressureHandler::uninstall() { }
 void MemoryPressureHandler::holdOff(unsigned) { }
index 189f488..2caca4e 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2011 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
@@ -32,6 +33,8 @@
 
 #if PLATFORM(IOS)
 #include <wtf/ThreadingPrimitives.h>
+#elif OS(LINUX)
+#include "Timer.h"
 #endif
 
 namespace WebCore {
@@ -69,6 +72,8 @@ public:
     WEBCORE_EXPORT void clearMemoryPressure();
     WEBCORE_EXPORT bool shouldWaitForMemoryClearMessage();
     void respondToMemoryPressureIfNeeded();
+#elif OS(LINUX)
+    static void waitForMemoryPressureEvent(void*);
 #endif
 
     class ReliefLogger {
@@ -127,9 +132,16 @@ private:
     void (^m_releaseMemoryBlock)();
     CFRunLoopObserverRef m_observer;
     Mutex m_observerMutex;
+#elif OS(LINUX)
+    int m_eventFD;
+    int m_pressureLevelFD;
+    WTF::ThreadIdentifier m_threadID;
+    Timer m_holdOffTimer;
+    void holdOffTimerFired();
+    void logErrorAndCloseFDs(const char* error);
 #endif
 };
+
 // Function to obtain the global memory pressure object.
 WEBCORE_EXPORT MemoryPressureHandler& memoryPressureHandler();
 
diff --git a/Source/WebCore/platform/linux/MemoryPressureHandlerLinux.cpp b/Source/WebCore/platform/linux/MemoryPressureHandlerLinux.cpp
new file mode 100644 (file)
index 0000000..2d30e29
--- /dev/null
@@ -0,0 +1,241 @@
+/*
+ * 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/Functional.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) {
+        char 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);
+}
+
+MemoryPressureHandler::~MemoryPressureHandler()
+{
+    uninstall();
+}
+
+void MemoryPressureHandler::waitForMemoryPressureEvent(void*)
+{
+    ASSERT(!isMainThread());
+    int eventFD = memoryPressureHandler().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;
+    }
+
+    memoryPressureHandler().m_underMemoryPressure = true;
+    callOnMainThread(bind(&MemoryPressureHandler::respondToMemoryPressure, &memoryPressureHandler(), true));
+}
+
+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;
+    }
+
+    m_underMemoryPressure = 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(bool critical)
+{
+    uninstall();
+
+    double startTime = monotonicallyIncreasingTime();
+    m_lowMemoryHandler(critical);
+    unsigned holdOffTime = (monotonicallyIncreasingTime() - startTime) * s_holdOffMultiplier;
+    holdOff(std::max(holdOffTime, s_minimumHoldOffTime));
+}
+
+void MemoryPressureHandler::platformReleaseMemory(bool)
+{
+    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 %ld bytes (from %ld to %ld)", m_logString, (memoryDiff * -1), m_initialMemory, currentMemory);
+    else if (memoryDiff > 0)
+        LOG(MemoryPressure, "Pressure relief: %s: +dirty %ld bytes (from %ld to %ld)", m_logString, memoryDiff, m_initialMemory, currentMemory);
+    else
+        LOG(MemoryPressure, "Pressure relief: %s: =dirty (at %ld bytes)", m_logString, 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); // 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)