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