b1be083a8994f0682963d52d0534a182274fdb71
[WebKit-https.git] / Source / WebCore / platform / linux / MemoryPressureHandlerLinux.cpp
1 /*
2  * Copyright (C) 2011, 2012 Apple Inc. All Rights Reserved.
3  * Copyright (C) 2014 Raspberry Pi Foundation. All Rights Reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 #include "config.h"
28 #include "MemoryPressureHandler.h"
29
30 #if OS(LINUX)
31
32 #include "Logging.h"
33
34 #include <fcntl.h>
35 #include <malloc.h>
36 #include <sys/eventfd.h>
37 #include <sys/stat.h>
38 #include <sys/types.h>
39 #include <unistd.h>
40 #include <wtf/CurrentTime.h>
41 #include <wtf/MainThread.h>
42 #include <wtf/text/WTFString.h>
43
44 namespace WebCore {
45
46 // Disable memory event reception for a minimum of s_minimumHoldOffTime
47 // seconds after receiving an event. Don't let events fire any sooner than
48 // s_holdOffMultiplier times the last cleanup processing time. Effectively
49 // this is 1 / s_holdOffMultiplier percent of the time.
50 // These value seems reasonable and testing verifies that it throttles frequent
51 // low memory events, greatly reducing CPU usage.
52 static const unsigned s_minimumHoldOffTime = 5;
53 static const unsigned s_holdOffMultiplier = 20;
54
55 static const char* s_cgroupMemoryPressureLevel = "/sys/fs/cgroup/memory/memory.pressure_level";
56 static const char* s_cgroupEventControl = "/sys/fs/cgroup/memory/cgroup.event_control";
57 static const char* s_processStatus = "/proc/self/status";
58
59 static inline String nextToken(FILE* file)
60 {
61     if (!file)
62         return String();
63
64     static const unsigned bufferSize = 128;
65     char buffer[bufferSize] = {0, };
66     unsigned index = 0;
67     while (index < bufferSize) {
68         char ch = fgetc(file);
69         if (ch == EOF || (isASCIISpace(ch) && index)) // Break on non-initial ASCII space.
70             break;
71         if (!isASCIISpace(ch)) {
72             buffer[index] = ch;
73             index++;
74         }
75     }
76
77     return String(buffer);
78 }
79
80 MemoryPressureHandler::~MemoryPressureHandler()
81 {
82     uninstall();
83 }
84
85 void MemoryPressureHandler::waitForMemoryPressureEvent(void*)
86 {
87     ASSERT(!isMainThread());
88     int eventFD = memoryPressureHandler().m_eventFD;
89     if (!eventFD) {
90         LOG(MemoryPressure, "Invalidate eventfd.");
91         return;
92     }
93
94     uint64_t buffer;
95     if (read(eventFD, &buffer, sizeof(buffer)) <= 0) {
96         LOG(MemoryPressure, "Failed to read eventfd.");
97         return;
98     }
99
100     // FIXME: Current memcg does not provide any way for users to know how serious the memory pressure is.
101     // So we assume all notifications from memcg are critical for now. If memcg had better inferfaces
102     // to get a detailed memory pressure level in the future, we should update here accordingly.
103     bool critical = true;
104     if (ReliefLogger::loggingEnabled())
105         LOG(MemoryPressure, "Got memory pressure notification (%s)", critical ? "critical" : "non-critical");
106
107     memoryPressureHandler().setUnderMemoryPressure(critical);
108     callOnMainThread([critical] {
109         memoryPressureHandler().respondToMemoryPressure(critical);
110     });
111 }
112
113 inline void MemoryPressureHandler::logErrorAndCloseFDs(const char* log)
114 {
115     if (log)
116         LOG(MemoryPressure, "%s, error : %m", log);
117
118     if (m_eventFD) {
119         close(m_eventFD);
120         m_eventFD = 0;
121     }
122     if (m_pressureLevelFD) {
123         close(m_pressureLevelFD);
124         m_pressureLevelFD = 0;
125     }
126 }
127
128 void MemoryPressureHandler::install()
129 {
130     if (m_installed)
131         return;
132
133     m_eventFD = eventfd(0, EFD_CLOEXEC);
134     if (m_eventFD == -1) {
135         LOG(MemoryPressure, "eventfd() failed: %m");
136         return;
137     }
138
139     m_pressureLevelFD = open(s_cgroupMemoryPressureLevel, O_CLOEXEC | O_RDONLY);
140     if (m_pressureLevelFD == -1) {
141         logErrorAndCloseFDs("Failed to open memory.pressure_level");
142         return;
143     }
144
145     int fd = open(s_cgroupEventControl, O_CLOEXEC | O_WRONLY);
146     if (fd == -1) {
147         logErrorAndCloseFDs("Failed to open cgroup.event_control");
148         return;
149     }
150
151     char line[128] = {0, };
152     if (snprintf(line, sizeof(line), "%d %d low", m_eventFD, m_pressureLevelFD) < 0
153         || write(fd, line, strlen(line) + 1) < 0) {
154         logErrorAndCloseFDs("Failed to write cgroup.event_control");
155         close(fd);
156         return;
157     }
158     close(fd);
159
160     m_threadID = createThread(waitForMemoryPressureEvent, this, "WebCore: MemoryPressureHandler");
161     if (!m_threadID) {
162         logErrorAndCloseFDs("Failed to create a thread for MemoryPressureHandler");
163         return;
164     }
165
166     if (ReliefLogger::loggingEnabled() && isUnderMemoryPressure())
167         LOG(MemoryPressure, "System is no longer under memory pressure.");
168
169     setUnderMemoryPressure(false);
170     m_installed = true;
171 }
172
173 void MemoryPressureHandler::uninstall()
174 {
175     if (!m_installed)
176         return;
177
178     if (m_threadID) {
179         detachThread(m_threadID);
180         m_threadID = 0;
181     }
182
183     logErrorAndCloseFDs(nullptr);
184     m_installed = false;
185 }
186
187 void MemoryPressureHandler::holdOffTimerFired()
188 {
189     install();
190 }
191
192 void MemoryPressureHandler::holdOff(unsigned seconds)
193 {
194     m_holdOffTimer.startOneShot(seconds);
195 }
196
197 void MemoryPressureHandler::respondToMemoryPressure(bool critical)
198 {
199     uninstall();
200
201     double startTime = monotonicallyIncreasingTime();
202     m_lowMemoryHandler(critical);
203     unsigned holdOffTime = (monotonicallyIncreasingTime() - startTime) * s_holdOffMultiplier;
204     holdOff(std::max(holdOffTime, s_minimumHoldOffTime));
205 }
206
207 void MemoryPressureHandler::platformReleaseMemory(bool)
208 {
209     ReliefLogger log("Run malloc_trim");
210     malloc_trim(0);
211 }
212
213 void MemoryPressureHandler::ReliefLogger::platformLog()
214 {
215     size_t currentMemory = platformMemoryUsage();
216     if (currentMemory == static_cast<size_t>(-1) || m_initialMemory == static_cast<size_t>(-1)) {
217         LOG(MemoryPressure, "%s (Unable to get dirty memory information for process)", m_logString);
218         return;
219     }
220
221     ssize_t memoryDiff = currentMemory - m_initialMemory;
222     if (memoryDiff < 0)
223         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));
224     else if (memoryDiff > 0)
225         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));
226     else
227         LOG(MemoryPressure, "Pressure relief: %s: =dirty (at %lu bytes)", m_logString, static_cast<unsigned long>(currentMemory));
228 }
229
230 size_t MemoryPressureHandler::ReliefLogger::platformMemoryUsage()
231 {
232     FILE* file = fopen(s_processStatus, "r");
233     if (!file)
234         return static_cast<size_t>(-1);
235
236     size_t vmSize = static_cast<size_t>(-1); // KB
237     String token = nextToken(file);
238     while (!token.isEmpty()) {
239         if (token == "VmSize:") {
240             vmSize = nextToken(file).toInt() * KB;
241             break;
242         }
243         token = nextToken(file);
244     }
245     fclose(file);
246
247     return vmSize;
248 }
249
250 } // namespace WebCore
251
252 #endif // OS(LINUX)