2 * Copyright (C) 2011-2017 Apple Inc. All Rights Reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #include "MemoryPressureHandler.h"
29 #include <wtf/MemoryFootprint.h>
30 #include <wtf/NeverDestroyed.h>
31 #include <wtf/RAMSize.h>
33 #define LOG_CHANNEL_PREFIX Log
37 #if RELEASE_LOG_DISABLED
38 WTFLogChannel LogMemoryPressure = { WTFLogChannelOn, "MemoryPressure", WTFLogLevelError };
40 WTFLogChannel LogMemoryPressure = { WTFLogChannelOn, "MemoryPressure", WTFLogLevelError, LOG_CHANNEL_WEBKIT_SUBSYSTEM, OS_LOG_DEFAULT };
43 WTF_EXPORT_PRIVATE bool MemoryPressureHandler::ReliefLogger::s_loggingEnabled = false;
45 MemoryPressureHandler& MemoryPressureHandler::singleton()
47 static NeverDestroyed<MemoryPressureHandler> memoryPressureHandler;
48 return memoryPressureHandler;
51 MemoryPressureHandler::MemoryPressureHandler()
53 : m_holdOffTimer(RunLoop::main(), this, &MemoryPressureHandler::holdOffTimerFired)
55 : m_windowsMeasurementTimer(RunLoop::main(), this, &MemoryPressureHandler::windowsMeasurementTimerFired)
60 void MemoryPressureHandler::setShouldUsePeriodicMemoryMonitor(bool use)
62 if (!isFastMallocEnabled()) {
63 // If we're running with FastMalloc disabled, some kind of testing or debugging is probably happening.
64 // Let's be nice and not enable the memory kill mechanism.
69 m_measurementTimer = std::make_unique<RunLoop::Timer<MemoryPressureHandler>>(RunLoop::main(), this, &MemoryPressureHandler::measurementTimerFired);
70 m_measurementTimer->startRepeating(30_s);
72 m_measurementTimer = nullptr;
75 #if !RELEASE_LOG_DISABLED
76 static const char* toString(MemoryUsagePolicy policy)
79 case MemoryUsagePolicy::Unrestricted: return "Unrestricted";
80 case MemoryUsagePolicy::Conservative: return "Conservative";
81 case MemoryUsagePolicy::Strict: return "Strict";
86 static size_t thresholdForMemoryKillWithProcessState(WebsamProcessState processState, unsigned tabCount)
88 size_t baseThreshold = 2 * GB;
89 #if CPU(X86_64) || CPU(ARM64)
90 if (processState == WebsamProcessState::Active)
91 baseThreshold = 4 * GB;
93 baseThreshold += std::min(tabCount - 1, 4u) * 1 * GB;
95 if ((tabCount > 1) || (processState == WebsamProcessState::Active))
96 baseThreshold = 3 * GB;
98 return std::min(baseThreshold, static_cast<size_t>(ramSize() * 0.9));
101 void MemoryPressureHandler::setPageCount(unsigned pageCount)
103 if (singleton().m_pageCount == pageCount)
105 singleton().m_pageCount = pageCount;
108 size_t MemoryPressureHandler::thresholdForMemoryKill()
110 return thresholdForMemoryKillWithProcessState(m_processState, m_pageCount);
113 static size_t thresholdForPolicy(MemoryUsagePolicy policy)
115 const size_t baseThresholdForPolicy = std::min(3 * GB, ramSize());
118 const double conservativeThresholdFraction = 0.5;
119 const double strictThresholdFraction = 0.65;
121 const double conservativeThresholdFraction = 0.33;
122 const double strictThresholdFraction = 0.5;
126 case MemoryUsagePolicy::Unrestricted:
128 case MemoryUsagePolicy::Conservative:
129 return baseThresholdForPolicy * conservativeThresholdFraction;
130 case MemoryUsagePolicy::Strict:
131 return baseThresholdForPolicy * strictThresholdFraction;
133 ASSERT_NOT_REACHED();
138 static MemoryUsagePolicy policyForFootprint(size_t footprint)
140 if (footprint >= thresholdForPolicy(MemoryUsagePolicy::Strict))
141 return MemoryUsagePolicy::Strict;
142 if (footprint >= thresholdForPolicy(MemoryUsagePolicy::Conservative))
143 return MemoryUsagePolicy::Conservative;
144 return MemoryUsagePolicy::Unrestricted;
147 MemoryUsagePolicy MemoryPressureHandler::currentMemoryUsagePolicy()
149 return policyForFootprint(memoryFootprint());
152 void MemoryPressureHandler::shrinkOrDie()
154 RELEASE_LOG(MemoryPressure, "Process is above the memory kill threshold. Trying to shrink down.");
155 releaseMemory(Critical::Yes, Synchronous::Yes);
157 size_t footprint = memoryFootprint();
158 RELEASE_LOG(MemoryPressure, "New memory footprint: %zu MB", footprint / MB);
160 if (footprint < thresholdForMemoryKill()) {
161 RELEASE_LOG(MemoryPressure, "Shrank below memory kill threshold. Process gets to live.");
162 setMemoryUsagePolicyBasedOnFootprint(footprint);
166 WTFLogAlways("Unable to shrink memory footprint of process (%zu MB) below the kill thresold (%zu MB). Killed\n", footprint / MB, thresholdForMemoryKill() / MB);
167 RELEASE_ASSERT(m_memoryKillCallback);
168 m_memoryKillCallback();
171 void MemoryPressureHandler::setMemoryUsagePolicyBasedOnFootprint(size_t footprint)
173 auto newPolicy = policyForFootprint(footprint);
174 if (newPolicy == m_memoryUsagePolicy)
177 RELEASE_LOG(MemoryPressure, "Memory usage policy changed: %s -> %s", toString(m_memoryUsagePolicy), toString(newPolicy));
178 m_memoryUsagePolicy = newPolicy;
179 memoryPressureStatusChanged();
182 void MemoryPressureHandler::measurementTimerFired()
184 size_t footprint = memoryFootprint();
185 RELEASE_LOG(MemoryPressure, "Current memory footprint: %zu MB", footprint / MB);
186 if (footprint >= thresholdForMemoryKill()) {
191 setMemoryUsagePolicyBasedOnFootprint(footprint);
193 switch (m_memoryUsagePolicy) {
194 case MemoryUsagePolicy::Unrestricted:
196 case MemoryUsagePolicy::Conservative:
197 releaseMemory(Critical::No, Synchronous::No);
199 case MemoryUsagePolicy::Strict:
200 releaseMemory(Critical::Yes, Synchronous::No);
204 if (processState() == WebsamProcessState::Active && footprint > thresholdForMemoryKillWithProcessState(WebsamProcessState::Inactive, m_pageCount))
205 doesExceedInactiveLimitWhileActive();
207 doesNotExceedInactiveLimitWhileActive();
210 void MemoryPressureHandler::doesExceedInactiveLimitWhileActive()
212 if (m_hasInvokedDidExceedInactiveLimitWhileActiveCallback)
214 if (m_didExceedInactiveLimitWhileActiveCallback)
215 m_didExceedInactiveLimitWhileActiveCallback();
216 m_hasInvokedDidExceedInactiveLimitWhileActiveCallback = true;
219 void MemoryPressureHandler::doesNotExceedInactiveLimitWhileActive()
221 m_hasInvokedDidExceedInactiveLimitWhileActiveCallback = false;
224 void MemoryPressureHandler::setProcessState(WebsamProcessState state)
226 if (m_processState == state)
228 m_processState = state;
231 void MemoryPressureHandler::beginSimulatedMemoryPressure()
233 if (m_isSimulatingMemoryPressure)
235 m_isSimulatingMemoryPressure = true;
236 memoryPressureStatusChanged();
237 respondToMemoryPressure(Critical::Yes, Synchronous::Yes);
240 void MemoryPressureHandler::endSimulatedMemoryPressure()
242 if (!m_isSimulatingMemoryPressure)
244 m_isSimulatingMemoryPressure = false;
245 memoryPressureStatusChanged();
248 void MemoryPressureHandler::releaseMemory(Critical critical, Synchronous synchronous)
250 if (!m_lowMemoryHandler)
253 ReliefLogger log("Total");
254 m_lowMemoryHandler(critical, synchronous);
255 platformReleaseMemory(critical);
258 void MemoryPressureHandler::setUnderMemoryPressure(bool underMemoryPressure)
260 if (m_underMemoryPressure == underMemoryPressure)
262 m_underMemoryPressure = underMemoryPressure;
263 memoryPressureStatusChanged();
266 void MemoryPressureHandler::memoryPressureStatusChanged()
268 if (m_memoryPressureStatusChangedCallback)
269 m_memoryPressureStatusChangedCallback(isUnderMemoryPressure());
272 void MemoryPressureHandler::ReliefLogger::logMemoryUsageChange()
274 #if !RELEASE_LOG_DISABLED
275 #define STRING_SPECIFICATION "%{public}s"
276 #define MEMORYPRESSURE_LOG(...) RELEASE_LOG(MemoryPressure, __VA_ARGS__)
278 #define STRING_SPECIFICATION "%s"
279 #define MEMORYPRESSURE_LOG(...) WTFLogAlways(__VA_ARGS__)
282 auto currentMemory = platformMemoryUsage();
283 if (!currentMemory || !m_initialMemory) {
284 MEMORYPRESSURE_LOG("Memory pressure relief: " STRING_SPECIFICATION ": (Unable to get dirty memory information for process)", m_logString);
288 long residentDiff = currentMemory->resident - m_initialMemory->resident;
289 long physicalDiff = currentMemory->physical - m_initialMemory->physical;
291 MEMORYPRESSURE_LOG("Memory pressure relief: " STRING_SPECIFICATION ": res = %zu/%zu/%ld, res+swap = %zu/%zu/%ld",
293 m_initialMemory->resident, currentMemory->resident, residentDiff,
294 m_initialMemory->physical, currentMemory->physical, physicalDiff);
297 #if !PLATFORM(COCOA) && !OS(LINUX) && !OS(WINDOWS)
298 void MemoryPressureHandler::install() { }
299 void MemoryPressureHandler::uninstall() { }
300 void MemoryPressureHandler::respondToMemoryPressure(Critical, Synchronous) { }
301 void MemoryPressureHandler::platformReleaseMemory(Critical) { }
302 std::optional<MemoryPressureHandler::ReliefLogger::MemoryUsage> MemoryPressureHandler::ReliefLogger::platformMemoryUsage() { return std::nullopt; }
306 void MemoryPressureHandler::platformInitialize() { }
309 } // namespace WebCore