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