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