1/*
2 * Copyright (C) 2016, 2018 Igalia S.L.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "MemoryPressureMonitor.h"
28
29#if OS(LINUX)
30
31#include "WebProcessPool.h"
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <unistd.h>
36#include <wtf/Threading.h>
37#include <wtf/UniStdExtras.h>
38
39namespace WebKit {
40
41static const size_t notSet = static_cast<size_t>(-1);
42
43static const Seconds s_minPollingInterval { 1_s };
44static const Seconds s_maxPollingInterval { 5_s };
45static const double s_minUsedMemoryPercentageForPolling = 50;
46static const double s_maxUsedMemoryPercentageForPolling = 85;
47static const int s_memoryPresurePercentageThreshold = 90;
48static const int s_memoryPresurePercentageThresholdCritical = 95;
49
50static size_t lowWatermarkPages()
51{
52 FILE* file = fopen("/proc/zoneinfo", "r");
53 if (!file)
54 return notSet;
55
56 size_t low = 0;
57 bool inZone = false;
58 bool foundLow = false;
59 char buffer[128];
60 while (char* line = fgets(buffer, 128, file)) {
61 if (!strncmp(line, "Node", 4)) {
62 inZone = true;
63 foundLow = false;
64 continue;
65 }
66
67 char* token = strtok(line, " ");
68 if (!token)
69 continue;
70
71 if (!strcmp(token, "low")) {
72 if (!inZone || foundLow) {
73 low = notSet;
74 break;
75 }
76 token = strtok(nullptr, " ");
77 if (!token) {
78 low = notSet;
79 break;
80 }
81 low += atoll(token);
82 foundLow = true;
83 }
84 }
85 fclose(file);
86
87 return low;
88}
89
90static inline size_t systemPageSize()
91{
92 static size_t pageSize = 0;
93 if (!pageSize)
94 pageSize = sysconf(_SC_PAGE_SIZE);
95 return pageSize;
96}
97
98// If MemAvailable was not present in /proc/meminfo, because it's an old kernel version,
99// we can do the same calculation with the information we have from meminfo and the low watermaks.
100// See https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773
101static size_t calculateMemoryAvailable(size_t memoryFree, size_t activeFile, size_t inactiveFile, size_t slabReclaimable)
102{
103 if (memoryFree == notSet || activeFile == notSet || inactiveFile == notSet || slabReclaimable == notSet)
104 return notSet;
105
106 size_t lowWatermark = lowWatermarkPages();
107 if (lowWatermark == notSet)
108 return notSet;
109
110 lowWatermark *= systemPageSize() / KB;
111
112 // Estimate the amount of memory available for userspace allocations, without causing swapping.
113 // Free memory cannot be taken below the low watermark, before the system starts swapping.
114 lowWatermark *= systemPageSize() / KB;
115 size_t memoryAvailable = memoryFree - lowWatermark;
116
117 // Not all the page cache can be freed, otherwise the system will start swapping. Assume at least
118 // half of the page cache, or the low watermark worth of cache, needs to stay.
119 size_t pageCache = activeFile + inactiveFile;
120 pageCache -= std::min(pageCache / 2, lowWatermark);
121 memoryAvailable += pageCache;
122
123 // Part of the reclaimable slab consists of items that are in use, and cannot be freed.
124 // Cap this estimate at the low watermark.
125 memoryAvailable += slabReclaimable - std::min(slabReclaimable / 2, lowWatermark);
126 return memoryAvailable;
127}
128
129static int systemMemoryUsedAsPercentage()
130{
131 FILE* file = fopen("/proc/meminfo", "r");
132 if (!file)
133 return -1;
134
135 size_t memoryAvailable, memoryTotal, memoryFree, activeFile, inactiveFile, slabReclaimable;
136 memoryAvailable = memoryTotal = memoryFree = activeFile = inactiveFile = slabReclaimable = notSet;
137 char buffer[128];
138 while (char* line = fgets(buffer, 128, file)) {
139 char* token = strtok(line, " ");
140 if (!token)
141 break;
142
143 if (!strcmp(token, "MemAvailable:")) {
144 if ((token = strtok(nullptr, " "))) {
145 memoryAvailable = atoll(token);
146 if (memoryTotal != notSet)
147 break;
148 }
149 } else if (!strcmp(token, "MemTotal:")) {
150 if ((token = strtok(nullptr, " ")))
151 memoryTotal = atoll(token);
152 else
153 break;
154 } else if (!strcmp(token, "MemFree:")) {
155 if ((token = strtok(nullptr, " ")))
156 memoryFree = atoll(token);
157 else
158 break;
159 } else if (!strcmp(token, "Active(file):")) {
160 if ((token = strtok(nullptr, " ")))
161 activeFile = atoll(token);
162 else
163 break;
164 } else if (!strcmp(token, "Inactive(file):")) {
165 if ((token = strtok(nullptr, " ")))
166 inactiveFile = atoll(token);
167 else
168 break;
169 } else if (!strcmp(token, "SReclaimable:")) {
170 if ((token = strtok(nullptr, " ")))
171 slabReclaimable = atoll(token);
172 else
173 break;
174 }
175
176 if (memoryTotal != notSet && memoryFree != notSet && activeFile != notSet && inactiveFile != notSet && slabReclaimable != notSet)
177 break;
178 }
179 fclose(file);
180
181 if (!memoryTotal || memoryTotal == notSet)
182 return -1;
183
184 if (memoryAvailable == notSet) {
185 memoryAvailable = calculateMemoryAvailable(memoryFree, activeFile, inactiveFile, slabReclaimable);
186 if (memoryAvailable == notSet)
187 return -1;
188 }
189
190 if (memoryAvailable > memoryTotal)
191 return -1;
192
193 return ((memoryTotal - memoryAvailable) * 100) / memoryTotal;
194}
195
196static inline Seconds pollIntervalForUsedMemoryPercentage(int usedPercentage)
197{
198 // Use a different poll interval depending on the currently memory used,
199 // to avoid polling too often when the system is under low memory usage.
200 if (usedPercentage < s_minUsedMemoryPercentageForPolling)
201 return s_maxPollingInterval;
202
203 if (usedPercentage >= s_maxUsedMemoryPercentageForPolling)
204 return s_minPollingInterval;
205
206 return s_minPollingInterval + (s_maxPollingInterval - s_minPollingInterval) *
207 ((s_maxUsedMemoryPercentageForPolling - usedPercentage) / (s_maxUsedMemoryPercentageForPolling - s_minUsedMemoryPercentageForPolling));
208}
209
210MemoryPressureMonitor& MemoryPressureMonitor::singleton()
211{
212 static NeverDestroyed<MemoryPressureMonitor> memoryMonitor;
213 return memoryMonitor;
214}
215
216void MemoryPressureMonitor::start()
217{
218 if (m_started)
219 return;
220
221 m_started = true;
222
223 Thread::create("MemoryPressureMonitor", [this] {
224 Seconds pollInterval = s_maxPollingInterval;
225 while (true) {
226 sleep(pollInterval);
227
228 int usedPercentage = systemMemoryUsedAsPercentage();
229 if (usedPercentage == -1) {
230 WTFLogAlways("Failed to get the memory usage");
231 break;
232 }
233
234 if (usedPercentage >= s_memoryPresurePercentageThreshold) {
235 bool isCritical = (usedPercentage >= s_memoryPresurePercentageThresholdCritical);
236 for (auto* processPool : WebProcessPool::allProcessPools())
237 processPool->sendMemoryPressureEvent(isCritical);
238 }
239 pollInterval = pollIntervalForUsedMemoryPercentage(usedPercentage);
240 }
241 })->detach();
242}
243
244
245} // namespace WebKit
246
247#endif // OS(LINUX)
248