/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__ |