/src/mozilla-central/xpcom/base/AvailableMemoryTracker.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "mozilla/AvailableMemoryTracker.h" |
8 | | |
9 | | #if defined(XP_WIN) |
10 | | #include "nsExceptionHandler.h" |
11 | | #include "nsICrashReporter.h" |
12 | | #include "nsIMemoryReporter.h" |
13 | | #include "nsMemoryPressure.h" |
14 | | #endif |
15 | | |
16 | | #include "nsIObserver.h" |
17 | | #include "nsIObserverService.h" |
18 | | #include "nsIRunnable.h" |
19 | | #include "nsISupports.h" |
20 | | #include "nsITimer.h" |
21 | | #include "nsThreadUtils.h" |
22 | | #include "nsXULAppAPI.h" |
23 | | |
24 | | #include "mozilla/ResultExtensions.h" |
25 | | #include "mozilla/Services.h" |
26 | | #include "mozilla/Unused.h" |
27 | | |
28 | | #if defined(MOZ_MEMORY) |
29 | | # include "mozmemory.h" |
30 | | #endif // MOZ_MEMORY |
31 | | |
32 | | using namespace mozilla; |
33 | | |
34 | | namespace { |
35 | | |
36 | | #if defined(XP_WIN) |
37 | | |
38 | | Atomic<uint32_t, MemoryOrdering::Relaxed> sNumLowVirtualMemEvents; |
39 | | Atomic<uint32_t, MemoryOrdering::Relaxed> sNumLowCommitSpaceEvents; |
40 | | Atomic<uint32_t, MemoryOrdering::Relaxed> sNumLowPhysicalMemEvents; |
41 | | |
42 | | class nsAvailableMemoryWatcher final : public nsIObserver, |
43 | | public nsITimerCallback |
44 | | { |
45 | | public: |
46 | | NS_DECL_ISUPPORTS |
47 | | NS_DECL_NSIOBSERVER |
48 | | NS_DECL_NSITIMERCALLBACK |
49 | | |
50 | | nsAvailableMemoryWatcher(); |
51 | | nsresult Init(); |
52 | | |
53 | | private: |
54 | | // Fire a low-memory notification if we have less than this many bytes of |
55 | | // virtual address space available. |
56 | | #if defined(HAVE_64BIT_BUILD) |
57 | | static const size_t kLowVirtualMemoryThreshold = 0; |
58 | | #else |
59 | | static const size_t kLowVirtualMemoryThreshold = 256 * 1024 * 1024; |
60 | | #endif |
61 | | |
62 | | // Fire a low-memory notification if we have less than this many bytes of |
63 | | // commit space (physical memory plus page file) left. |
64 | | static const size_t kLowCommitSpaceThreshold = 256 * 1024 * 1024; |
65 | | |
66 | | // Fire a low-memory notification if we have less than this many bytes of |
67 | | // physical memory available on the whole machine. |
68 | | static const size_t kLowPhysicalMemoryThreshold = 0; |
69 | | |
70 | | // Don't fire a low-memory notification because of low available physical |
71 | | // memory or low commit space more often than this interval. |
72 | | static const uint32_t kLowMemoryNotificationIntervalMS = 10000; |
73 | | |
74 | | // Poll the amount of free memory at this rate. |
75 | | static const uint32_t kPollingIntervalMS = 1000; |
76 | | |
77 | | // Observer topics we subscribe to, see below. |
78 | | static const char* const kObserverTopics[]; |
79 | | |
80 | | static bool IsVirtualMemoryLow(const MEMORYSTATUSEX& aStat); |
81 | | static bool IsCommitSpaceLow(const MEMORYSTATUSEX& aStat); |
82 | | static bool IsPhysicalMemoryLow(const MEMORYSTATUSEX& aStat); |
83 | | |
84 | | ~nsAvailableMemoryWatcher() {}; |
85 | | bool OngoingMemoryPressure() { return mUnderMemoryPressure; } |
86 | | void AdjustPollingInterval(const bool aLowMemory); |
87 | | void SendMemoryPressureEvent(); |
88 | | void MaybeSaveMemoryReport(); |
89 | | void Shutdown(); |
90 | | |
91 | | nsCOMPtr<nsITimer> mTimer; |
92 | | bool mUnderMemoryPressure; |
93 | | bool mSavedReport; |
94 | | }; |
95 | | |
96 | | const char* const nsAvailableMemoryWatcher::kObserverTopics[] = { |
97 | | "quit-application", |
98 | | "user-interaction-active", |
99 | | "user-interaction-inactive", |
100 | | }; |
101 | | |
102 | | NS_IMPL_ISUPPORTS(nsAvailableMemoryWatcher, nsIObserver, nsITimerCallback) |
103 | | |
104 | | nsAvailableMemoryWatcher::nsAvailableMemoryWatcher() |
105 | | : mTimer(nullptr) |
106 | | , mUnderMemoryPressure(false) |
107 | | , mSavedReport(false) |
108 | | { |
109 | | } |
110 | | |
111 | | nsresult |
112 | | nsAvailableMemoryWatcher::Init() |
113 | | { |
114 | | mTimer = NS_NewTimer(); |
115 | | |
116 | | nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); |
117 | | MOZ_ASSERT(observerService); |
118 | | |
119 | | for (auto topic : kObserverTopics) { |
120 | | nsresult rv = observerService->AddObserver(this, topic, |
121 | | /* ownsWeak */ false); |
122 | | NS_ENSURE_SUCCESS(rv, rv); |
123 | | } |
124 | | |
125 | | MOZ_TRY(mTimer->InitWithCallback(this, kPollingIntervalMS, |
126 | | nsITimer::TYPE_REPEATING_SLACK)); |
127 | | return NS_OK; |
128 | | } |
129 | | |
130 | | void |
131 | | nsAvailableMemoryWatcher::Shutdown() |
132 | | { |
133 | | nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); |
134 | | MOZ_ASSERT(observerService); |
135 | | |
136 | | for (auto topic : kObserverTopics) { |
137 | | Unused << observerService->RemoveObserver(this, topic); |
138 | | } |
139 | | |
140 | | if (mTimer) { |
141 | | mTimer->Cancel(); |
142 | | mTimer = nullptr; |
143 | | } |
144 | | } |
145 | | |
146 | | /* static */ bool |
147 | | nsAvailableMemoryWatcher::IsVirtualMemoryLow(const MEMORYSTATUSEX& aStat) |
148 | | { |
149 | | if ((kLowVirtualMemoryThreshold != 0) && |
150 | | (aStat.ullAvailVirtual < kLowVirtualMemoryThreshold)) { |
151 | | sNumLowVirtualMemEvents++; |
152 | | return true; |
153 | | } |
154 | | |
155 | | return false; |
156 | | } |
157 | | |
158 | | /* static */ bool |
159 | | nsAvailableMemoryWatcher::IsCommitSpaceLow(const MEMORYSTATUSEX& aStat) |
160 | | { |
161 | | if ((kLowCommitSpaceThreshold != 0) && |
162 | | (aStat.ullAvailPageFile < kLowCommitSpaceThreshold)) { |
163 | | sNumLowCommitSpaceEvents++; |
164 | | CrashReporter::AnnotateCrashReport( |
165 | | CrashReporter::Annotation::LowCommitSpaceEvents, |
166 | | uint32_t(sNumLowCommitSpaceEvents)); |
167 | | return true; |
168 | | } |
169 | | |
170 | | return false; |
171 | | } |
172 | | |
173 | | /* static */ bool |
174 | | nsAvailableMemoryWatcher::IsPhysicalMemoryLow(const MEMORYSTATUSEX& aStat) |
175 | | { |
176 | | if ((kLowPhysicalMemoryThreshold != 0) && |
177 | | (aStat.ullAvailPhys < kLowPhysicalMemoryThreshold)) { |
178 | | sNumLowPhysicalMemEvents++; |
179 | | return true; |
180 | | } |
181 | | |
182 | | return false; |
183 | | } |
184 | | |
185 | | void |
186 | | nsAvailableMemoryWatcher::SendMemoryPressureEvent() |
187 | | { |
188 | | MemoryPressureState state = OngoingMemoryPressure() ? MemPressure_Ongoing |
189 | | : MemPressure_New; |
190 | | NS_DispatchEventualMemoryPressure(state); |
191 | | } |
192 | | |
193 | | void |
194 | | nsAvailableMemoryWatcher::MaybeSaveMemoryReport() |
195 | | { |
196 | | if (!mSavedReport && OngoingMemoryPressure()) { |
197 | | nsCOMPtr<nsICrashReporter> cr = |
198 | | do_GetService("@mozilla.org/toolkit/crash-reporter;1"); |
199 | | if (cr) { |
200 | | if (NS_SUCCEEDED(cr->SaveMemoryReport())) { |
201 | | mSavedReport = true; |
202 | | } |
203 | | } |
204 | | } |
205 | | } |
206 | | |
207 | | void |
208 | | nsAvailableMemoryWatcher::AdjustPollingInterval(const bool aLowMemory) |
209 | | { |
210 | | if (aLowMemory) { |
211 | | // We entered a low-memory state, wait for a longer interval before polling |
212 | | // again as there's no point in rapidly sending further notifications. |
213 | | mTimer->SetDelay(kLowMemoryNotificationIntervalMS); |
214 | | } else if (OngoingMemoryPressure()) { |
215 | | // We were under memory pressure but we're not anymore, resume polling at |
216 | | // a faster pace. |
217 | | mTimer->SetDelay(kPollingIntervalMS); |
218 | | } |
219 | | } |
220 | | |
221 | | // Timer callback, polls memory stats to detect low-memory conditions. This |
222 | | // will send memory-pressure events if memory is running low and adjust the |
223 | | // polling interval accordingly. |
224 | | NS_IMETHODIMP |
225 | | nsAvailableMemoryWatcher::Notify(nsITimer* aTimer) |
226 | | { |
227 | | MEMORYSTATUSEX stat; |
228 | | stat.dwLength = sizeof(stat); |
229 | | bool success = GlobalMemoryStatusEx(&stat); |
230 | | |
231 | | if (success) { |
232 | | bool lowMemory = |
233 | | IsVirtualMemoryLow(stat) || |
234 | | IsCommitSpaceLow(stat) || |
235 | | IsPhysicalMemoryLow(stat); |
236 | | |
237 | | if (lowMemory) { |
238 | | SendMemoryPressureEvent(); |
239 | | MaybeSaveMemoryReport(); |
240 | | } else { |
241 | | mSavedReport = false; // Save a new report if memory gets low again |
242 | | } |
243 | | |
244 | | AdjustPollingInterval(lowMemory); |
245 | | mUnderMemoryPressure = lowMemory; |
246 | | } |
247 | | |
248 | | return NS_OK; |
249 | | } |
250 | | |
251 | | // Observer service callback, used to stop the polling timer when the user |
252 | | // stops interacting with Firefox and resuming it when they interact again. |
253 | | // Also used to shut down the service if the application is quitting. |
254 | | NS_IMETHODIMP |
255 | | nsAvailableMemoryWatcher::Observe(nsISupports* aSubject, const char* aTopic, |
256 | | const char16_t* aData) |
257 | | { |
258 | | if (strcmp(aTopic, "quit-application") == 0) { |
259 | | Shutdown(); |
260 | | } else if (strcmp(aTopic, "user-interaction-inactive") == 0) { |
261 | | mTimer->Cancel(); |
262 | | } else if (strcmp(aTopic, "user-interaction-active") == 0) { |
263 | | mTimer->InitWithCallback(this, kPollingIntervalMS, |
264 | | nsITimer::TYPE_REPEATING_SLACK); |
265 | | } else { |
266 | | MOZ_ASSERT_UNREACHABLE("Unknown topic"); |
267 | | } |
268 | | |
269 | | return NS_OK; |
270 | | } |
271 | | |
272 | | static int64_t |
273 | | LowMemoryEventsVirtualDistinguishedAmount() |
274 | | { |
275 | | return sNumLowVirtualMemEvents; |
276 | | } |
277 | | |
278 | | static int64_t |
279 | | LowMemoryEventsCommitSpaceDistinguishedAmount() |
280 | | { |
281 | | return sNumLowCommitSpaceEvents; |
282 | | } |
283 | | |
284 | | static int64_t |
285 | | LowMemoryEventsPhysicalDistinguishedAmount() |
286 | | { |
287 | | return sNumLowPhysicalMemEvents; |
288 | | } |
289 | | |
290 | | class LowEventsReporter final : public nsIMemoryReporter |
291 | | { |
292 | | ~LowEventsReporter() {} |
293 | | |
294 | | public: |
295 | | NS_DECL_ISUPPORTS |
296 | | |
297 | | NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, |
298 | | nsISupports* aData, bool aAnonymize) override |
299 | | { |
300 | | MOZ_COLLECT_REPORT( |
301 | | "low-memory-events/virtual", KIND_OTHER, UNITS_COUNT_CUMULATIVE, |
302 | | LowMemoryEventsVirtualDistinguishedAmount(), |
303 | | "Number of low-virtual-memory events fired since startup. We fire such an " |
304 | | "event if we notice there is less than memory.low_virtual_mem_threshold_mb of " |
305 | | "virtual address space available (if zero, this behavior is disabled). The " |
306 | | "process will probably crash if it runs out of virtual address space, so " |
307 | | "this event is dire."); |
308 | | |
309 | | MOZ_COLLECT_REPORT( |
310 | | "low-memory-events/commit-space", KIND_OTHER, UNITS_COUNT_CUMULATIVE, |
311 | | LowMemoryEventsCommitSpaceDistinguishedAmount(), |
312 | | "Number of low-commit-space events fired since startup. We fire such an " |
313 | | "event if we notice there is less than memory.low_commit_space_threshold_mb of " |
314 | | "commit space available (if zero, this behavior is disabled). Windows will " |
315 | | "likely kill the process if it runs out of commit space, so this event is " |
316 | | "dire."); |
317 | | |
318 | | MOZ_COLLECT_REPORT( |
319 | | "low-memory-events/physical", KIND_OTHER, UNITS_COUNT_CUMULATIVE, |
320 | | LowMemoryEventsPhysicalDistinguishedAmount(), |
321 | | "Number of low-physical-memory events fired since startup. We fire such an " |
322 | | "event if we notice there is less than memory.low_physical_memory_threshold_mb " |
323 | | "of physical memory available (if zero, this behavior is disabled). The " |
324 | | "machine will start to page if it runs out of physical memory. This may " |
325 | | "cause it to run slowly, but it shouldn't cause it to crash."); |
326 | | |
327 | | return NS_OK; |
328 | | } |
329 | | }; |
330 | | NS_IMPL_ISUPPORTS(LowEventsReporter, nsIMemoryReporter) |
331 | | |
332 | | #endif // defined(XP_WIN) |
333 | | |
334 | | /** |
335 | | * This runnable is executed in response to a memory-pressure event; we spin |
336 | | * the event-loop when receiving the memory-pressure event in the hope that |
337 | | * other observers will synchronously free some memory that we'll be able to |
338 | | * purge here. |
339 | | */ |
340 | | class nsJemallocFreeDirtyPagesRunnable final : public nsIRunnable |
341 | | { |
342 | 0 | ~nsJemallocFreeDirtyPagesRunnable() {} |
343 | | |
344 | | public: |
345 | | NS_DECL_ISUPPORTS |
346 | | NS_DECL_NSIRUNNABLE |
347 | | }; |
348 | | |
349 | | NS_IMPL_ISUPPORTS(nsJemallocFreeDirtyPagesRunnable, nsIRunnable) |
350 | | |
351 | | NS_IMETHODIMP |
352 | | nsJemallocFreeDirtyPagesRunnable::Run() |
353 | 0 | { |
354 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
355 | 0 |
|
356 | | #if defined(MOZ_MEMORY) |
357 | | jemalloc_free_dirty_pages(); |
358 | | #endif |
359 | |
|
360 | 0 | return NS_OK; |
361 | 0 | } |
362 | | |
363 | | /** |
364 | | * The memory pressure watcher is used for listening to memory-pressure events |
365 | | * and reacting upon them. We use one instance per process currently only for |
366 | | * cleaning up dirty unused pages held by jemalloc. |
367 | | */ |
368 | | class nsMemoryPressureWatcher final : public nsIObserver |
369 | | { |
370 | 0 | ~nsMemoryPressureWatcher() {} |
371 | | |
372 | | public: |
373 | | NS_DECL_ISUPPORTS |
374 | | NS_DECL_NSIOBSERVER |
375 | | |
376 | | void Init(); |
377 | | }; |
378 | | |
379 | | NS_IMPL_ISUPPORTS(nsMemoryPressureWatcher, nsIObserver) |
380 | | |
381 | | /** |
382 | | * Initialize and subscribe to the memory-pressure events. We subscribe to the |
383 | | * observer service in this method and not in the constructor because we need |
384 | | * to hold a strong reference to 'this' before calling the observer service. |
385 | | */ |
386 | | void |
387 | | nsMemoryPressureWatcher::Init() |
388 | 3 | { |
389 | 3 | nsCOMPtr<nsIObserverService> os = services::GetObserverService(); |
390 | 3 | |
391 | 3 | if (os) { |
392 | 3 | os->AddObserver(this, "memory-pressure", /* ownsWeak */ false); |
393 | 3 | } |
394 | 3 | } |
395 | | |
396 | | /** |
397 | | * Reacts to all types of memory-pressure events, launches a runnable to |
398 | | * free dirty pages held by jemalloc. |
399 | | */ |
400 | | NS_IMETHODIMP |
401 | | nsMemoryPressureWatcher::Observe(nsISupports* aSubject, const char* aTopic, |
402 | | const char16_t* aData) |
403 | 0 | { |
404 | 0 | MOZ_ASSERT(!strcmp(aTopic, "memory-pressure"), "Unknown topic"); |
405 | 0 |
|
406 | 0 | nsCOMPtr<nsIRunnable> runnable = new nsJemallocFreeDirtyPagesRunnable(); |
407 | 0 |
|
408 | 0 | NS_DispatchToMainThread(runnable); |
409 | 0 |
|
410 | 0 | return NS_OK; |
411 | 0 | } |
412 | | |
413 | | } // namespace |
414 | | |
415 | | namespace mozilla { |
416 | | namespace AvailableMemoryTracker { |
417 | | |
418 | | void |
419 | | Init() |
420 | 3 | { |
421 | 3 | // The watchers are held alive by the observer service. |
422 | 3 | RefPtr<nsMemoryPressureWatcher> watcher = new nsMemoryPressureWatcher(); |
423 | 3 | watcher->Init(); |
424 | 3 | |
425 | | #if defined(XP_WIN) |
426 | | RegisterStrongMemoryReporter(new LowEventsReporter()); |
427 | | RegisterLowMemoryEventsVirtualDistinguishedAmount( |
428 | | LowMemoryEventsVirtualDistinguishedAmount); |
429 | | RegisterLowMemoryEventsCommitSpaceDistinguishedAmount( |
430 | | LowMemoryEventsCommitSpaceDistinguishedAmount); |
431 | | RegisterLowMemoryEventsPhysicalDistinguishedAmount( |
432 | | LowMemoryEventsPhysicalDistinguishedAmount); |
433 | | |
434 | | if (XRE_IsParentProcess()) { |
435 | | RefPtr<nsAvailableMemoryWatcher> poller = new nsAvailableMemoryWatcher(); |
436 | | |
437 | | if (NS_FAILED(poller->Init())) { |
438 | | NS_WARNING("Could not start the available memory watcher"); |
439 | | } |
440 | | } |
441 | | #endif // defined(XP_WIN) |
442 | | } |
443 | | |
444 | | } // namespace AvailableMemoryTracker |
445 | | } // namespace mozilla |