Coverage Report

Created: 2018-09-25 14:53

/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