Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/xpcom/base/nsMemoryReporterManager.h
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
#ifndef nsMemoryReporterManager_h__
8
#define nsMemoryReporterManager_h__
9
10
#include "mozilla/Mutex.h"
11
#include "nsDataHashtable.h"
12
#include "nsHashKeys.h"
13
#include "nsIEventTarget.h"
14
#include "nsIMemoryReporter.h"
15
#include "nsITimer.h"
16
#include "nsServiceManagerUtils.h"
17
#include "nsDataHashtable.h"
18
19
namespace mozilla {
20
class MemoryReportingProcess;
21
namespace dom {
22
class MemoryReport;
23
} // namespace dom
24
} // namespace mozilla
25
26
class nsITimer;
27
28
class nsMemoryReporterManager final : public nsIMemoryReporterManager,
29
                                      public nsIMemoryReporter
30
{
31
  virtual ~nsMemoryReporterManager();
32
33
public:
34
  NS_DECL_THREADSAFE_ISUPPORTS
35
  NS_DECL_NSIMEMORYREPORTERMANAGER
36
  NS_DECL_NSIMEMORYREPORTER
37
38
  MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
39
40
  nsMemoryReporterManager();
41
42
  // Gets the memory reporter manager service.
43
  static nsMemoryReporterManager* GetOrCreate()
44
73
  {
45
73
    nsCOMPtr<nsIMemoryReporterManager> imgr =
46
73
      do_GetService("@mozilla.org/memory-reporter-manager;1");
47
73
    return static_cast<nsMemoryReporterManager*>(imgr.get());
48
73
  }
49
50
  typedef nsDataHashtable<nsRefPtrHashKey<nsIMemoryReporter>, bool> StrongReportersTable;
51
  typedef nsDataHashtable<nsPtrHashKey<nsIMemoryReporter>, bool> WeakReportersTable;
52
53
  // Inter-process memory reporting proceeds as follows.
54
  //
55
  // - GetReports() (declared within NS_DECL_NSIMEMORYREPORTERMANAGER)
56
  //   synchronously gets memory reports for the current process, sets up some
57
  //   state (mPendingProcessesState) for when child processes report back --
58
  //   including a timer -- and starts telling child processes to get memory
59
  //   reports.  Control then returns to the main event loop.
60
  //
61
  //   The number of concurrent child process reports is limited by the pref
62
  //   "memory.report_concurrency" in order to prevent the memory overhead of
63
  //   memory reporting from causing problems, especially on B2G when swapping
64
  //   to compressed RAM; see bug 1154053.
65
  //
66
  // - HandleChildReport() is called (asynchronously) once per child process
67
  //   reporter callback.
68
  //
69
  // - EndProcessReport() is called (asynchronously) once per process that
70
  //   finishes reporting back, including the parent.  If all processes do so
71
  //   before time-out, the timer is cancelled.  If there are child processes
72
  //   whose requests have not yet been sent, they will be started until the
73
  //   concurrency limit is (again) reached.
74
  //
75
  // - TimeoutCallback() is called (asynchronously) if all the child processes
76
  //   don't respond within the time threshold.
77
  //
78
  // - FinishReporting() finishes things off.  It is *always* called -- either
79
  //   from EndChildReport() (if all child processes have reported back) or
80
  //   from TimeoutCallback() (if time-out occurs).
81
  //
82
  // All operations occur on the main thread.
83
  //
84
  // The above sequence of steps is a "request".  A partially-completed request
85
  // is described as "in flight".
86
  //
87
  // Each request has a "generation", a unique number that identifies it.  This
88
  // is used to ensure that each reports from a child process corresponds to
89
  // the appropriate request from the parent process.  (It's easier to
90
  // implement a generation system than to implement a child report request
91
  // cancellation mechanism.)
92
  //
93
  // Failures are mostly ignored, because it's (a) typically the most sensible
94
  // thing to do, and (b) often hard to do anything else.  The following are
95
  // the failure cases of note.
96
  //
97
  // - If a request is made while the previous request is in flight, the new
98
  //   request is ignored, as per getReports()'s specification.  No error is
99
  //   reported, because the previous request will complete soon enough.
100
  //
101
  // - If one or more child processes fail to respond within the time limit,
102
  //   things will proceed as if they don't exist.  No error is reported,
103
  //   because partial information is better than nothing.
104
  //
105
  // - If a child process reports after the time-out occurs, it is ignored.
106
  //   (Generation checking will ensure it is ignored even if a subsequent
107
  //   request is in flight;  this is the main use of generations.)  No error
108
  //   is reported, because there's nothing sensible to be done about it at
109
  //   this late stage.
110
  //
111
  // - If the time-out occurs after a child process has sent some reports but
112
  //   before it has signaled completion (see bug 1151597), then what it
113
  //   successfully sent will be included, with no explicit indication that it
114
  //   is incomplete.
115
  //
116
  // Now, what what happens if a child process is created/destroyed in the
117
  // middle of a request?  Well, PendingProcessesState is initialized with an array
118
  // of child process actors as of when the report started.  So...
119
  //
120
  // - If a process is created after reporting starts, it won't be sent a
121
  //   request for reports.  So the reported data will reflect how things were
122
  //   when the request began.
123
  //
124
  // - If a process is destroyed before it starts reporting back, the reported
125
  //   data will reflect how things are when the request ends.
126
  //
127
  // - If a process is destroyed after it starts reporting back but before it
128
  //   finishes, the reported data will contain a partial report for it.
129
  //
130
  // - If a process is destroyed after reporting back, but before all other
131
  //   child processes have reported back, it will be included in the reported
132
  //   data.  So the reported data will reflect how things were when the
133
  //   request began.
134
  //
135
  // The inconsistencies between these cases are unfortunate but difficult to
136
  // avoid.  It's enough of an edge case to not be worth doing more.
137
  //
138
  void HandleChildReport(uint32_t aGeneration,
139
                         const mozilla::dom::MemoryReport& aChildReport);
140
  void EndProcessReport(uint32_t aGeneration, bool aSuccess);
141
142
  // Functions that (a) implement distinguished amounts, and (b) are outside of
143
  // this module.
144
  struct AmountFns
145
  {
146
    mozilla::InfallibleAmountFn mJSMainRuntimeGCHeap;
147
    mozilla::InfallibleAmountFn mJSMainRuntimeTemporaryPeak;
148
    mozilla::InfallibleAmountFn mJSMainRuntimeRealmsSystem;
149
    mozilla::InfallibleAmountFn mJSMainRuntimeRealmsUser;
150
151
    mozilla::InfallibleAmountFn mImagesContentUsedUncompressed;
152
153
    mozilla::InfallibleAmountFn mStorageSQLite;
154
155
    mozilla::InfallibleAmountFn mLowMemoryEventsVirtual;
156
    mozilla::InfallibleAmountFn mLowMemoryEventsCommitSpace;
157
    mozilla::InfallibleAmountFn mLowMemoryEventsPhysical;
158
159
    mozilla::InfallibleAmountFn mGhostWindows;
160
161
    AmountFns()
162
3
    {
163
3
      mozilla::PodZero(this);
164
3
    }
165
  };
166
  AmountFns mAmountFns;
167
168
  // Convenience function to get RSS easily from other code.  This is useful
169
  // when debugging transient memory spikes with printf instrumentation.
170
  static int64_t ResidentFast();
171
172
  // Convenience function to get peak RSS easily from other code.
173
  static int64_t ResidentPeak();
174
175
  // Convenience function to get USS easily from other code.  This is useful
176
  // when debugging unshared memory pages for forked processes.
177
  static int64_t ResidentUnique();
178
179
  // Functions that measure per-tab memory consumption.
180
  struct SizeOfTabFns
181
  {
182
    mozilla::JSSizeOfTabFn    mJS;
183
    mozilla::NonJSSizeOfTabFn mNonJS;
184
185
    SizeOfTabFns()
186
3
    {
187
3
      mozilla::PodZero(this);
188
3
    }
189
  };
190
  SizeOfTabFns mSizeOfTabFns;
191
192
private:
193
  MOZ_MUST_USE nsresult
194
  RegisterReporterHelper(nsIMemoryReporter* aReporter,
195
                         bool aForce, bool aStrongRef, bool aIsAsync);
196
197
  MOZ_MUST_USE nsresult StartGettingReports();
198
  // No MOZ_MUST_USE here because ignoring the result is common and reasonable.
199
  nsresult FinishReporting();
200
201
  void DispatchReporter(nsIMemoryReporter* aReporter, bool aIsAsync,
202
                        nsIHandleReportCallback* aHandleReport,
203
                        nsISupports* aHandleReportData,
204
                        bool aAnonymize);
205
206
  static void TimeoutCallback(nsITimer* aTimer, void* aData);
207
  // Note: this timeout needs to be long enough to allow for the
208
  // possibility of DMD reports and/or running on a low-end phone.
209
  static const uint32_t kTimeoutLengthMS = 50000;
210
211
  mozilla::Mutex mMutex;
212
  bool mIsRegistrationBlocked;
213
214
  StrongReportersTable* mStrongReporters;
215
  WeakReportersTable* mWeakReporters;
216
217
  // These two are only used for testing purposes.
218
  StrongReportersTable* mSavedStrongReporters;
219
  WeakReportersTable* mSavedWeakReporters;
220
221
  uint32_t mNextGeneration;
222
223
  // Used to keep track of state of which processes are currently running and
224
  // waiting to run memory reports. Holds references to parameters needed when
225
  // requesting a memory report and finishing reporting.
226
  struct PendingProcessesState
227
  {
228
    uint32_t                             mGeneration;
229
    bool                                 mAnonymize;
230
    bool                                 mMinimize;
231
    nsCOMPtr<nsITimer>                   mTimer;
232
    nsTArray<RefPtr<mozilla::MemoryReportingProcess>> mChildrenPending;
233
    uint32_t                             mNumProcessesRunning;
234
    uint32_t                             mNumProcessesCompleted;
235
    uint32_t                             mConcurrencyLimit;
236
    nsCOMPtr<nsIHandleReportCallback>    mHandleReport;
237
    nsCOMPtr<nsISupports>                mHandleReportData;
238
    nsCOMPtr<nsIFinishReportingCallback> mFinishReporting;
239
    nsCOMPtr<nsISupports>                mFinishReportingData;
240
    nsString                             mDMDDumpIdent;
241
242
    PendingProcessesState(uint32_t aGeneration, bool aAnonymize, bool aMinimize,
243
                          uint32_t aConcurrencyLimit,
244
                          nsIHandleReportCallback* aHandleReport,
245
                          nsISupports* aHandleReportData,
246
                          nsIFinishReportingCallback* aFinishReporting,
247
                          nsISupports* aFinishReportingData,
248
                          const nsAString& aDMDDumpIdent);
249
  };
250
251
  // Used to keep track of the state of the asynchronously run memory
252
  // reporters. The callback and file handle used when all memory reporters
253
  // have finished are also stored here.
254
  struct PendingReportersState
255
  {
256
    // Number of memory reporters currently running.
257
    uint32_t mReportsPending;
258
259
    // Callback for when all memory reporters have completed.
260
    nsCOMPtr<nsIFinishReportingCallback> mFinishReporting;
261
    nsCOMPtr<nsISupports> mFinishReportingData;
262
263
    // File handle to write a DMD report to if requested.
264
    FILE* mDMDFile;
265
266
    PendingReportersState(nsIFinishReportingCallback* aFinishReporting,
267
                        nsISupports* aFinishReportingData,
268
                        FILE* aDMDFile)
269
      : mReportsPending(0)
270
      , mFinishReporting(aFinishReporting)
271
      , mFinishReportingData(aFinishReportingData)
272
      , mDMDFile(aDMDFile)
273
0
    {
274
0
    }
275
  };
276
277
  // When this is non-null, a request is in flight.  Note: We use manual
278
  // new/delete for this because its lifetime doesn't match block scope or
279
  // anything like that.
280
  PendingProcessesState* mPendingProcessesState;
281
282
  // This is reinitialized each time a call to GetReports is initiated.
283
  PendingReportersState* mPendingReportersState;
284
285
  // Used in GetHeapAllocatedAsync() to run jemalloc_stats async.
286
  nsCOMPtr<nsIEventTarget> mThreadPool;
287
288
  PendingProcessesState* GetStateForGeneration(uint32_t aGeneration);
289
  static MOZ_MUST_USE bool
290
  StartChildReport(mozilla::MemoryReportingProcess* aChild,
291
                   const PendingProcessesState* aState);
292
};
293
294
#define NS_MEMORY_REPORTER_MANAGER_CID \
295
{ 0xfb97e4f5, 0x32dd, 0x497a, \
296
{ 0xba, 0xa2, 0x7d, 0x1e, 0x55, 0x7, 0x99, 0x10 } }
297
298
#endif // nsMemoryReporterManager_h__