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 | |
39 | namespace WebKit { |
40 | |
41 | static const size_t notSet = static_cast<size_t>(-1); |
42 | |
43 | static const Seconds s_minPollingInterval { 1_s }; |
44 | static const Seconds s_maxPollingInterval { 5_s }; |
45 | static const double s_minUsedMemoryPercentageForPolling = 50; |
46 | static const double s_maxUsedMemoryPercentageForPolling = 85; |
47 | static const int s_memoryPresurePercentageThreshold = 90; |
48 | static const int s_memoryPresurePercentageThresholdCritical = 95; |
49 | |
50 | static 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 | |
90 | static 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 |
101 | static 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 | |
129 | static 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 | |
196 | static 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 | |
210 | MemoryPressureMonitor& MemoryPressureMonitor::singleton() |
211 | { |
212 | static NeverDestroyed<MemoryPressureMonitor> memoryMonitor; |
213 | return memoryMonitor; |
214 | } |
215 | |
216 | void 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 | |