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 #include "ProfilerParent.h"
8 
9 #ifdef MOZ_GECKO_PROFILER
10 #  include "nsProfiler.h"
11 #endif
12 
13 #include "mozilla/BaseProfilerDetail.h"
14 #include "mozilla/ClearOnShutdown.h"
15 #include "mozilla/DataMutex.h"
16 #include "mozilla/IOInterposer.h"
17 #include "mozilla/ipc/Endpoint.h"
18 #include "mozilla/Maybe.h"
19 #include "mozilla/ProfileBufferControlledChunkManager.h"
20 #include "mozilla/RefPtr.h"
21 #include "mozilla/Unused.h"
22 #include "nsTArray.h"
23 #include "nsThreadUtils.h"
24 
25 #include <utility>
26 
27 namespace mozilla {
28 
29 using namespace ipc;
30 
31 /* static */
CreateForProcess(base::ProcessId aOtherPid)32 Endpoint<PProfilerChild> ProfilerParent::CreateForProcess(
33     base::ProcessId aOtherPid) {
34   MOZ_RELEASE_ASSERT(NS_IsMainThread());
35   Endpoint<PProfilerChild> child;
36 #ifdef MOZ_GECKO_PROFILER
37   Endpoint<PProfilerParent> parent;
38   nsresult rv = PProfiler::CreateEndpoints(base::GetCurrentProcId(), aOtherPid,
39                                            &parent, &child);
40 
41   if (NS_FAILED(rv)) {
42     MOZ_CRASH("Failed to create top level actor for PProfiler!");
43   }
44 
45   RefPtr<ProfilerParent> actor = new ProfilerParent(aOtherPid);
46   if (!parent.Bind(actor)) {
47     MOZ_CRASH("Failed to bind parent actor for PProfiler!");
48   }
49 
50   // mSelfRef will be cleared in DeallocPProfilerParent.
51   actor->mSelfRef = actor;
52   actor->Init();
53 #endif
54 
55   return child;
56 }
57 
58 #ifdef MOZ_GECKO_PROFILER
59 
60 class ProfilerParentTracker;
61 
62 // This class is responsible for gathering updates from chunk managers in
63 // different process, and request for the oldest chunks to be destroyed whenever
64 // the given memory limit is reached.
65 class ProfileBufferGlobalController final {
66  public:
67   explicit ProfileBufferGlobalController(size_t aMaximumBytes);
68 
69   ~ProfileBufferGlobalController();
70 
71   void HandleChildChunkManagerUpdate(
72       base::ProcessId aProcessId,
73       ProfileBufferControlledChunkManager::Update&& aUpdate);
74 
75   static bool IsLockedOnCurrentThread();
76 
77  private:
78   void HandleChunkManagerNonFinalUpdate(
79       base::ProcessId aProcessId,
80       ProfileBufferControlledChunkManager::Update&& aUpdate,
81       ProfileBufferControlledChunkManager& aParentChunkManager);
82 
83   const size_t mMaximumBytes;
84 
85   const base::ProcessId mParentProcessId = base::GetCurrentProcId();
86 
87   struct ParentChunkManagerAndPendingUpdate {
88     ProfileBufferControlledChunkManager* mChunkManager = nullptr;
89     ProfileBufferControlledChunkManager::Update mPendingUpdate;
90   };
91 
92   static DataMutexBase<ParentChunkManagerAndPendingUpdate,
93                        baseprofiler::detail::BaseProfilerMutex>
94       sParentChunkManagerAndPendingUpdate;
95 
96   size_t mUnreleasedTotalBytes = 0;
97 
98   struct PidAndBytes {
99     base::ProcessId mProcessId;
100     size_t mBytes;
101 
102     // For searching and sorting.
operator ==mozilla::ProfileBufferGlobalController::PidAndBytes103     bool operator==(base::ProcessId aSearchedProcessId) const {
104       return mProcessId == aSearchedProcessId;
105     }
operator ==mozilla::ProfileBufferGlobalController::PidAndBytes106     bool operator==(const PidAndBytes& aOther) const {
107       return mProcessId == aOther.mProcessId;
108     }
operator <mozilla::ProfileBufferGlobalController::PidAndBytes109     bool operator<(base::ProcessId aSearchedProcessId) const {
110       return mProcessId < aSearchedProcessId;
111     }
operator <mozilla::ProfileBufferGlobalController::PidAndBytes112     bool operator<(const PidAndBytes& aOther) const {
113       return mProcessId < aOther.mProcessId;
114     }
115   };
116   using PidAndBytesArray = nsTArray<PidAndBytes>;
117   PidAndBytesArray mUnreleasedBytesByPid;
118 
119   size_t mReleasedTotalBytes = 0;
120 
121   struct TimeStampAndBytesAndPid {
122     TimeStamp mTimeStamp;
123     size_t mBytes;
124     base::ProcessId mProcessId;
125 
126     // For searching and sorting.
operator ==mozilla::ProfileBufferGlobalController::TimeStampAndBytesAndPid127     bool operator==(const TimeStampAndBytesAndPid& aOther) const {
128       // Sort first by timestamps, and then by pid in rare cases with the same
129       // timestamps.
130       return mTimeStamp == aOther.mTimeStamp && mProcessId == aOther.mProcessId;
131     }
operator <mozilla::ProfileBufferGlobalController::TimeStampAndBytesAndPid132     bool operator<(const TimeStampAndBytesAndPid& aOther) const {
133       // Sort first by timestamps, and then by pid in rare cases with the same
134       // timestamps.
135       return mTimeStamp < aOther.mTimeStamp ||
136              (MOZ_UNLIKELY(mTimeStamp == aOther.mTimeStamp) &&
137               mProcessId < aOther.mProcessId);
138     }
139   };
140   using TimeStampAndBytesAndPidArray = nsTArray<TimeStampAndBytesAndPid>;
141   TimeStampAndBytesAndPidArray mReleasedChunksByTime;
142 };
143 
144 /* static */
145 DataMutexBase<ProfileBufferGlobalController::ParentChunkManagerAndPendingUpdate,
146               baseprofiler::detail::BaseProfilerMutex>
147     ProfileBufferGlobalController::sParentChunkManagerAndPendingUpdate{
148         "ProfileBufferGlobalController::sParentChunkManagerAndPendingUpdate"};
149 
150 // This singleton class tracks live ProfilerParent's (meaning there's a current
151 // connection with a child process).
152 // It also knows when the local profiler is running.
153 // And when both the profiler is running and at least one child is present, it
154 // creates a ProfileBufferGlobalController and forwards chunk updates to it.
155 class ProfilerParentTracker final {
156  public:
157   static void StartTracking(ProfilerParent* aParent);
158   static void StopTracking(ProfilerParent* aParent);
159 
160   static void ProfilerStarted(uint32_t aEntries);
161   static void ProfilerWillStopIfStarted();
162 
163   template <typename FuncType>
164   static void Enumerate(FuncType&& aIterFunc);
165 
166   template <typename FuncType>
167   static void ForChild(base::ProcessId aChildPid, FuncType&& aIterFunc);
168 
169   static void ForwardChildChunkManagerUpdate(
170       base::ProcessId aProcessId,
171       ProfileBufferControlledChunkManager::Update&& aUpdate);
172 
173   ProfilerParentTracker();
174   ~ProfilerParentTracker();
175 
176  private:
177   // Get the singleton instance; Create one on the first request, unless we are
178   // past XPCOMShutdownThreads, which is when it should get destroyed.
179   static ProfilerParentTracker* GetInstance();
180 
181   // List of parents for currently-connected child processes.
182   nsTArray<ProfilerParent*> mProfilerParents;
183 
184   // If non-0, the parent profiler is running, with this limit (in number of
185   // entries.) This is needed here, because the parent profiler may start
186   // running before child processes are known (e.g., startup profiling).
187   uint32_t mEntries = 0;
188 
189   // When the profiler is running and there is at least one parent-child
190   // connection, this is the controller that should receive chunk updates.
191   Maybe<ProfileBufferGlobalController> mMaybeController;
192 };
193 
ProfileBufferGlobalController(size_t aMaximumBytes)194 ProfileBufferGlobalController::ProfileBufferGlobalController(
195     size_t aMaximumBytes)
196     : mMaximumBytes(aMaximumBytes) {
197   MOZ_RELEASE_ASSERT(NS_IsMainThread());
198 
199   // This is the local chunk manager for this parent process, so updates can be
200   // handled here.
201   ProfileBufferControlledChunkManager* parentChunkManager =
202       profiler_get_controlled_chunk_manager();
203 
204   if (NS_WARN_IF(!parentChunkManager)) {
205     return;
206   }
207 
208   {
209     auto lockedParentChunkManagerAndPendingUpdate =
210         sParentChunkManagerAndPendingUpdate.Lock();
211     lockedParentChunkManagerAndPendingUpdate->mChunkManager =
212         parentChunkManager;
213   }
214 
215   parentChunkManager->SetUpdateCallback(
216       [this](ProfileBufferControlledChunkManager::Update&& aUpdate) {
217         MOZ_ASSERT(!aUpdate.IsNotUpdate(),
218                    "Update callback should never be given a non-update");
219         auto lockedParentChunkManagerAndPendingUpdate =
220             sParentChunkManagerAndPendingUpdate.Lock();
221         if (aUpdate.IsFinal()) {
222           // Final update of the parent.
223           // We cannot keep the chunk manager, and there's no point handling
224           // updates anymore. Do some cleanup now, to free resources before
225           // we're destroyed.
226           lockedParentChunkManagerAndPendingUpdate->mChunkManager = nullptr;
227           lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.Clear();
228           mUnreleasedTotalBytes = 0;
229           mUnreleasedBytesByPid.Clear();
230           mReleasedTotalBytes = 0;
231           mReleasedChunksByTime.Clear();
232           return;
233         }
234         if (!lockedParentChunkManagerAndPendingUpdate->mChunkManager) {
235           // No chunk manager, ignore updates.
236           return;
237         }
238         // Special handling of parent non-final updates:
239         // These updates are coming from *this* process, and may originate from
240         // scopes in any thread where any lock is held, so using other locks (to
241         // e.g., dispatch tasks or send IPCs) could trigger a deadlock. Instead,
242         // parent updates are stored locally and handled when the next
243         // non-parent update needs handling, see HandleChildChunkManagerUpdate.
244         lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.Fold(
245             std::move(aUpdate));
246       });
247 }
248 
~ProfileBufferGlobalController()249 ProfileBufferGlobalController ::~ProfileBufferGlobalController() {
250   MOZ_RELEASE_ASSERT(NS_IsMainThread());
251   // Extract the parent chunk manager (if still set).
252   // This means any update after this will be ignored.
253   ProfileBufferControlledChunkManager* parentChunkManager = []() {
254     auto lockedParentChunkManagerAndPendingUpdate =
255         sParentChunkManagerAndPendingUpdate.Lock();
256     lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.Clear();
257     return std::exchange(
258         lockedParentChunkManagerAndPendingUpdate->mChunkManager, nullptr);
259   }();
260   if (parentChunkManager) {
261     // We had not received a final update yet, so the chunk manager is still
262     // valid. Reset the callback in the chunk manager, this will immediately
263     // invoke the callback with the final empty update; see handling above.
264     parentChunkManager->SetUpdateCallback({});
265   }
266 }
267 
HandleChildChunkManagerUpdate(base::ProcessId aProcessId,ProfileBufferControlledChunkManager::Update && aUpdate)268 void ProfileBufferGlobalController::HandleChildChunkManagerUpdate(
269     base::ProcessId aProcessId,
270     ProfileBufferControlledChunkManager::Update&& aUpdate) {
271   MOZ_RELEASE_ASSERT(NS_IsMainThread());
272 
273   MOZ_ASSERT(aProcessId != mParentProcessId);
274 
275   MOZ_ASSERT(!aUpdate.IsNotUpdate(),
276              "HandleChildChunkManagerUpdate should not be given a non-update");
277 
278   auto lockedParentChunkManagerAndPendingUpdate =
279       sParentChunkManagerAndPendingUpdate.Lock();
280   if (!lockedParentChunkManagerAndPendingUpdate->mChunkManager) {
281     // No chunk manager, ignore updates.
282     return;
283   }
284 
285   if (aUpdate.IsFinal()) {
286     // Final update in a child process, remove all traces of that process.
287     size_t index = mUnreleasedBytesByPid.BinaryIndexOf(aProcessId);
288     if (index != PidAndBytesArray::NoIndex) {
289       // We already have a value for this pid.
290       PidAndBytes& pidAndBytes = mUnreleasedBytesByPid[index];
291       mUnreleasedTotalBytes -= pidAndBytes.mBytes;
292       mUnreleasedBytesByPid.RemoveElementAt(index);
293     }
294 
295     size_t released = 0;
296     mReleasedChunksByTime.RemoveElementsBy(
297         [&released, aProcessId](const auto& chunk) {
298           const bool match = chunk.mProcessId == aProcessId;
299           if (match) {
300             released += chunk.mBytes;
301           }
302           return match;
303         });
304     if (released != 0) {
305       mReleasedTotalBytes -= released;
306     }
307 
308     // Total can only have gone down, so there's no need to check the limit.
309     return;
310   }
311 
312   // Non-final update in child process.
313 
314   // Before handling the child update, we may have pending updates from the
315   // parent, which can be processed now since we're in an IPC callback outside
316   // of any profiler-related scope.
317   if (!lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.IsNotUpdate()) {
318     MOZ_ASSERT(
319         !lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.IsFinal());
320     HandleChunkManagerNonFinalUpdate(
321         mParentProcessId,
322         std::move(lockedParentChunkManagerAndPendingUpdate->mPendingUpdate),
323         *lockedParentChunkManagerAndPendingUpdate->mChunkManager);
324     lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.Clear();
325   }
326 
327   HandleChunkManagerNonFinalUpdate(
328       aProcessId, std::move(aUpdate),
329       *lockedParentChunkManagerAndPendingUpdate->mChunkManager);
330 }
331 
332 /* static */
IsLockedOnCurrentThread()333 bool ProfileBufferGlobalController::IsLockedOnCurrentThread() {
334   return sParentChunkManagerAndPendingUpdate.Mutex().IsLockedOnCurrentThread();
335 }
336 
HandleChunkManagerNonFinalUpdate(base::ProcessId aProcessId,ProfileBufferControlledChunkManager::Update && aUpdate,ProfileBufferControlledChunkManager & aParentChunkManager)337 void ProfileBufferGlobalController::HandleChunkManagerNonFinalUpdate(
338     base::ProcessId aProcessId,
339     ProfileBufferControlledChunkManager::Update&& aUpdate,
340     ProfileBufferControlledChunkManager& aParentChunkManager) {
341   MOZ_ASSERT(!aUpdate.IsFinal());
342 
343   size_t index = mUnreleasedBytesByPid.BinaryIndexOf(aProcessId);
344   if (index != PidAndBytesArray::NoIndex) {
345     // We already have a value for this pid.
346     PidAndBytes& pidAndBytes = mUnreleasedBytesByPid[index];
347     mUnreleasedTotalBytes =
348         mUnreleasedTotalBytes - pidAndBytes.mBytes + aUpdate.UnreleasedBytes();
349     pidAndBytes.mBytes = aUpdate.UnreleasedBytes();
350   } else {
351     // New pid.
352     mUnreleasedBytesByPid.InsertElementSorted(
353         PidAndBytes{aProcessId, aUpdate.UnreleasedBytes()});
354     mUnreleasedTotalBytes += aUpdate.UnreleasedBytes();
355   }
356 
357   size_t destroyedReleased = 0;
358   if (!aUpdate.OldestDoneTimeStamp().IsNull()) {
359     size_t i = 0;
360     for (; i < mReleasedChunksByTime.Length(); ++i) {
361       if (mReleasedChunksByTime[i].mTimeStamp >=
362           aUpdate.OldestDoneTimeStamp()) {
363         break;
364       }
365     }
366     // Here, i is the index of the first item that's at or after
367     // aUpdate.mOldestDoneTimeStamp, so chunks from aProcessId before that have
368     // been destroyed.
369     while (i != 0) {
370       --i;
371       const TimeStampAndBytesAndPid& item = mReleasedChunksByTime[i];
372       if (item.mProcessId == aProcessId) {
373         destroyedReleased += item.mBytes;
374         mReleasedChunksByTime.RemoveElementAt(i);
375       }
376     }
377   }
378 
379   size_t newlyReleased = 0;
380   for (const ProfileBufferControlledChunkManager::ChunkMetadata& chunk :
381        aUpdate.NewlyReleasedChunksRef()) {
382     newlyReleased += chunk.mBufferBytes;
383     mReleasedChunksByTime.InsertElementSorted(TimeStampAndBytesAndPid{
384         chunk.mDoneTimeStamp, chunk.mBufferBytes, aProcessId});
385   }
386 
387   mReleasedTotalBytes = mReleasedTotalBytes - destroyedReleased + newlyReleased;
388 
389 #  ifdef DEBUG
390   size_t totalReleased = 0;
391   for (const TimeStampAndBytesAndPid& item : mReleasedChunksByTime) {
392     totalReleased += item.mBytes;
393   }
394   MOZ_ASSERT(mReleasedTotalBytes == totalReleased);
395 #  endif  // DEBUG
396 
397   std::vector<ProfileBufferControlledChunkManager::ChunkMetadata> toDestroy;
398   while (mUnreleasedTotalBytes + mReleasedTotalBytes > mMaximumBytes &&
399          !mReleasedChunksByTime.IsEmpty()) {
400     // We have reached the global memory limit, and there *are* released chunks
401     // that can be destroyed. Start with the first one, which is the oldest.
402     const TimeStampAndBytesAndPid& oldest = mReleasedChunksByTime[0];
403     mReleasedTotalBytes -= oldest.mBytes;
404     if (oldest.mProcessId == mParentProcessId) {
405       aParentChunkManager.DestroyChunksAtOrBefore(oldest.mTimeStamp);
406     } else {
407       ProfilerParentTracker::ForChild(
408           oldest.mProcessId,
409           [timestamp = oldest.mTimeStamp](ProfilerParent* profilerParent) {
410             Unused << profilerParent->SendDestroyReleasedChunksAtOrBefore(
411                 timestamp);
412           });
413     }
414     mReleasedChunksByTime.RemoveElementAt(0);
415   }
416 }
417 
418 /* static */
GetInstance()419 ProfilerParentTracker* ProfilerParentTracker::GetInstance() {
420   MOZ_RELEASE_ASSERT(NS_IsMainThread());
421 
422   // The main instance pointer, it will be initialized at most once, before
423   // XPCOMShutdownThreads.
424   static UniquePtr<ProfilerParentTracker> instance = nullptr;
425   if (MOZ_UNLIKELY(!instance)) {
426     if (PastShutdownPhase(ShutdownPhase::XPCOMShutdownThreads)) {
427       return nullptr;
428     }
429 
430     instance = MakeUnique<ProfilerParentTracker>();
431 
432     // The tracker should get destroyed before threads are shutdown, because its
433     // destruction closes extant channels, which could trigger promise
434     // rejections that need to be dispatched to other threads.
435     ClearOnShutdown(&instance, ShutdownPhase::XPCOMShutdownThreads);
436   }
437 
438   return instance.get();
439 }
440 
441 /* static */
StartTracking(ProfilerParent * aProfilerParent)442 void ProfilerParentTracker::StartTracking(ProfilerParent* aProfilerParent) {
443   ProfilerParentTracker* tracker = GetInstance();
444   if (!tracker) {
445     return;
446   }
447 
448   if (tracker->mMaybeController.isNothing() && tracker->mEntries != 0) {
449     // There is no controller yet, but the profiler has started.
450     // Since we're adding a ProfilerParent, it's a good time to start
451     // controlling the global memory usage of the profiler.
452     // (And this helps delay the Controller startup, because the parent profiler
453     // can start *very* early in the process, when some resources like threads
454     // are not ready yet.)
455     tracker->mMaybeController.emplace(size_t(tracker->mEntries) * 8u);
456   }
457 
458   tracker->mProfilerParents.AppendElement(aProfilerParent);
459 }
460 
461 /* static */
StopTracking(ProfilerParent * aParent)462 void ProfilerParentTracker::StopTracking(ProfilerParent* aParent) {
463   ProfilerParentTracker* tracker = GetInstance();
464   if (!tracker) {
465     return;
466   }
467 
468   tracker->mProfilerParents.RemoveElement(aParent);
469 }
470 
471 /* static */
ProfilerStarted(uint32_t aEntries)472 void ProfilerParentTracker::ProfilerStarted(uint32_t aEntries) {
473   ProfilerParentTracker* tracker = GetInstance();
474   if (!tracker) {
475     return;
476   }
477 
478   tracker->mEntries = aEntries;
479 
480   if (tracker->mMaybeController.isNothing() &&
481       !tracker->mProfilerParents.IsEmpty()) {
482     // We are already tracking child processes, so it's a good time to start
483     // controlling the global memory usage of the profiler.
484     tracker->mMaybeController.emplace(size_t(tracker->mEntries) * 8u);
485   }
486 }
487 
488 /* static */
ProfilerWillStopIfStarted()489 void ProfilerParentTracker::ProfilerWillStopIfStarted() {
490   ProfilerParentTracker* tracker = GetInstance();
491   if (!tracker) {
492     return;
493   }
494 
495   tracker->mEntries = 0;
496   tracker->mMaybeController = Nothing{};
497 }
498 
499 template <typename FuncType>
500 /* static */
Enumerate(FuncType && aIterFunc)501 void ProfilerParentTracker::Enumerate(FuncType&& aIterFunc) {
502   ProfilerParentTracker* tracker = GetInstance();
503   if (!tracker) {
504     return;
505   }
506 
507   for (ProfilerParent* profilerParent : tracker->mProfilerParents) {
508     if (!profilerParent->mDestroyed) {
509       aIterFunc(profilerParent);
510     }
511   }
512 }
513 
514 template <typename FuncType>
515 /* static */
ForChild(base::ProcessId aChildPid,FuncType && aIterFunc)516 void ProfilerParentTracker::ForChild(base::ProcessId aChildPid,
517                                      FuncType&& aIterFunc) {
518   ProfilerParentTracker* tracker = GetInstance();
519   if (!tracker) {
520     return;
521   }
522 
523   for (ProfilerParent* profilerParent : tracker->mProfilerParents) {
524     if (profilerParent->mChildPid == aChildPid) {
525       if (!profilerParent->mDestroyed) {
526         std::forward<FuncType>(aIterFunc)(profilerParent);
527       }
528       return;
529     }
530   }
531 }
532 
533 /* static */
ForwardChildChunkManagerUpdate(base::ProcessId aProcessId,ProfileBufferControlledChunkManager::Update && aUpdate)534 void ProfilerParentTracker::ForwardChildChunkManagerUpdate(
535     base::ProcessId aProcessId,
536     ProfileBufferControlledChunkManager::Update&& aUpdate) {
537   ProfilerParentTracker* tracker = GetInstance();
538   if (!tracker || tracker->mMaybeController.isNothing()) {
539     return;
540   }
541 
542   MOZ_ASSERT(!aUpdate.IsNotUpdate(),
543              "No process should ever send a non-update");
544   tracker->mMaybeController->HandleChildChunkManagerUpdate(aProcessId,
545                                                            std::move(aUpdate));
546 }
547 
ProfilerParentTracker()548 ProfilerParentTracker::ProfilerParentTracker() {
549   MOZ_RELEASE_ASSERT(NS_IsMainThread());
550   MOZ_COUNT_CTOR(ProfilerParentTracker);
551 }
552 
~ProfilerParentTracker()553 ProfilerParentTracker::~ProfilerParentTracker() {
554   // This destructor should only be called on the main thread.
555   MOZ_RELEASE_ASSERT(NS_IsMainThread() ||
556                      // OR we're not on the main thread (including if we are
557                      // past the end of `main()`), which is fine *if* there are
558                      // no ProfilerParent's still registered, in which case
559                      // nothing else will happen in this destructor anyway.
560                      // See bug 1713971 for more information.
561                      mProfilerParents.IsEmpty());
562   MOZ_COUNT_DTOR(ProfilerParentTracker);
563 
564   // Close the channels of any profiler parents that haven't been destroyed.
565   for (ProfilerParent* profilerParent : mProfilerParents.Clone()) {
566     if (!profilerParent->mDestroyed) {
567       // Keep the object alive until the call to Close() has completed.
568       // Close() will trigger a call to DeallocPProfilerParent.
569       RefPtr<ProfilerParent> actor = profilerParent;
570       actor->Close();
571     }
572   }
573 }
574 
ProfilerParent(base::ProcessId aChildPid)575 ProfilerParent::ProfilerParent(base::ProcessId aChildPid)
576     : mChildPid(aChildPid), mDestroyed(false) {
577   MOZ_COUNT_CTOR(ProfilerParent);
578 
579   MOZ_RELEASE_ASSERT(NS_IsMainThread());
580 }
581 
Init()582 void ProfilerParent::Init() {
583   MOZ_RELEASE_ASSERT(NS_IsMainThread());
584 
585   ProfilerParentTracker::StartTracking(this);
586 
587   // We propagated the profiler state from the parent process to the child
588   // process through MOZ_PROFILER_STARTUP* environment variables.
589   // However, the profiler state might have changed in this process since then,
590   // and now that an active communication channel has been established with the
591   // child process, it's a good time to sync up the two profilers again.
592 
593   int entries = 0;
594   Maybe<double> duration = Nothing();
595   double interval = 0;
596   mozilla::Vector<const char*> filters;
597   uint32_t features;
598   uint64_t activeTabID;
599   profiler_get_start_params(&entries, &duration, &interval, &features, &filters,
600                             &activeTabID);
601 
602   if (entries != 0) {
603     ProfilerInitParams ipcParams;
604     ipcParams.enabled() = true;
605     ipcParams.entries() = entries;
606     ipcParams.duration() = duration;
607     ipcParams.interval() = interval;
608     ipcParams.features() = features;
609     ipcParams.activeTabID() = activeTabID;
610 
611     for (uint32_t i = 0; i < filters.length(); ++i) {
612       ipcParams.filters().AppendElement(filters[i]);
613     }
614 
615     Unused << SendEnsureStarted(ipcParams);
616     RequestChunkManagerUpdate();
617   } else {
618     Unused << SendStop();
619   }
620 }
621 
~ProfilerParent()622 ProfilerParent::~ProfilerParent() {
623   MOZ_COUNT_DTOR(ProfilerParent);
624 
625   MOZ_RELEASE_ASSERT(NS_IsMainThread());
626   ProfilerParentTracker::StopTracking(this);
627 }
628 
629 /* static */
630 nsTArray<RefPtr<ProfilerParent::SingleProcessProfilePromise>>
GatherProfiles()631 ProfilerParent::GatherProfiles() {
632   if (!NS_IsMainThread()) {
633     return nsTArray<RefPtr<ProfilerParent::SingleProcessProfilePromise>>();
634   }
635 
636   nsTArray<RefPtr<SingleProcessProfilePromise>> results;
637   ProfilerParentTracker::Enumerate([&](ProfilerParent* profilerParent) {
638     results.AppendElement(profilerParent->SendGatherProfile());
639   });
640   return results;
641 }
642 
643 // Magic value for ProfileBufferChunkManagerUpdate::unreleasedBytes meaning
644 // that this is a final update from a child.
645 constexpr static uint64_t scUpdateUnreleasedBytesFINAL = uint64_t(-1);
646 
647 /* static */
MakeFinalUpdate()648 ProfileBufferChunkManagerUpdate ProfilerParent::MakeFinalUpdate() {
649   return ProfileBufferChunkManagerUpdate{
650       uint64_t(scUpdateUnreleasedBytesFINAL), 0, TimeStamp{},
651       nsTArray<ProfileBufferChunkMetadata>{}};
652 }
653 
654 /* static */
IsLockedOnCurrentThread()655 bool ProfilerParent::IsLockedOnCurrentThread() {
656   return ProfileBufferGlobalController::IsLockedOnCurrentThread();
657 }
658 
RequestChunkManagerUpdate()659 void ProfilerParent::RequestChunkManagerUpdate() {
660   if (mDestroyed) {
661     return;
662   }
663 
664   RefPtr<AwaitNextChunkManagerUpdatePromise> updatePromise =
665       SendAwaitNextChunkManagerUpdate();
666   updatePromise->Then(
667       GetMainThreadSerialEventTarget(), __func__,
668       [self = RefPtr<ProfilerParent>(this)](
669           const ProfileBufferChunkManagerUpdate& aUpdate) {
670         if (aUpdate.unreleasedBytes() == scUpdateUnreleasedBytesFINAL) {
671           // Special value meaning it's the final update from that child.
672           ProfilerParentTracker::ForwardChildChunkManagerUpdate(
673               self->mChildPid,
674               ProfileBufferControlledChunkManager::Update(nullptr));
675         } else {
676           // Not the final update, translate it.
677           std::vector<ProfileBufferControlledChunkManager::ChunkMetadata>
678               chunks;
679           if (!aUpdate.newlyReleasedChunks().IsEmpty()) {
680             chunks.reserve(aUpdate.newlyReleasedChunks().Length());
681             for (const ProfileBufferChunkMetadata& chunk :
682                  aUpdate.newlyReleasedChunks()) {
683               chunks.emplace_back(chunk.doneTimeStamp(), chunk.bufferBytes());
684             }
685           }
686           // Let the tracker handle it.
687           ProfilerParentTracker::ForwardChildChunkManagerUpdate(
688               self->mChildPid,
689               ProfileBufferControlledChunkManager::Update(
690                   aUpdate.unreleasedBytes(), aUpdate.releasedBytes(),
691                   aUpdate.oldestDoneTimeStamp(), std::move(chunks)));
692           // This was not a final update, so start a new request.
693           self->RequestChunkManagerUpdate();
694         }
695       },
696       [self = RefPtr<ProfilerParent>(this)](
697           mozilla::ipc::ResponseRejectReason aReason) {
698         // Rejection could be for a number of reasons, assume the child will
699         // not respond anymore, so we pretend we received a final update.
700         ProfilerParentTracker::ForwardChildChunkManagerUpdate(
701             self->mChildPid,
702             ProfileBufferControlledChunkManager::Update(nullptr));
703       });
704 }
705 
706 /* static */
ProfilerStarted(nsIProfilerStartParams * aParams)707 void ProfilerParent::ProfilerStarted(nsIProfilerStartParams* aParams) {
708   if (!NS_IsMainThread()) {
709     return;
710   }
711 
712   ProfilerInitParams ipcParams;
713   double duration;
714   ipcParams.enabled() = true;
715   aParams->GetEntries(&ipcParams.entries());
716   aParams->GetDuration(&duration);
717   if (duration > 0.0) {
718     ipcParams.duration() = Some(duration);
719   } else {
720     ipcParams.duration() = Nothing();
721   }
722   aParams->GetInterval(&ipcParams.interval());
723   aParams->GetFeatures(&ipcParams.features());
724   ipcParams.filters() = aParams->GetFilters().Clone();
725   aParams->GetActiveTabID(&ipcParams.activeTabID());
726 
727   ProfilerParentTracker::ProfilerStarted(ipcParams.entries());
728   ProfilerParentTracker::Enumerate([&](ProfilerParent* profilerParent) {
729     Unused << profilerParent->SendStart(ipcParams);
730     profilerParent->RequestChunkManagerUpdate();
731   });
732 }
733 
734 /* static */
ProfilerWillStopIfStarted()735 void ProfilerParent::ProfilerWillStopIfStarted() {
736   if (!NS_IsMainThread()) {
737     return;
738   }
739 
740   ProfilerParentTracker::ProfilerWillStopIfStarted();
741 }
742 
743 /* static */
ProfilerStopped()744 void ProfilerParent::ProfilerStopped() {
745   if (!NS_IsMainThread()) {
746     return;
747   }
748 
749   ProfilerParentTracker::Enumerate([](ProfilerParent* profilerParent) {
750     Unused << profilerParent->SendStop();
751   });
752 }
753 
754 /* static */
ProfilerPaused()755 void ProfilerParent::ProfilerPaused() {
756   if (!NS_IsMainThread()) {
757     return;
758   }
759 
760   ProfilerParentTracker::Enumerate([](ProfilerParent* profilerParent) {
761     Unused << profilerParent->SendPause();
762   });
763 }
764 
765 /* static */
ProfilerResumed()766 void ProfilerParent::ProfilerResumed() {
767   if (!NS_IsMainThread()) {
768     return;
769   }
770 
771   ProfilerParentTracker::Enumerate([](ProfilerParent* profilerParent) {
772     Unused << profilerParent->SendResume();
773   });
774 }
775 
776 /* static */
ProfilerPausedSampling()777 void ProfilerParent::ProfilerPausedSampling() {
778   if (!NS_IsMainThread()) {
779     return;
780   }
781 
782   ProfilerParentTracker::Enumerate([](ProfilerParent* profilerParent) {
783     Unused << profilerParent->SendPauseSampling();
784   });
785 }
786 
787 /* static */
ProfilerResumedSampling()788 void ProfilerParent::ProfilerResumedSampling() {
789   if (!NS_IsMainThread()) {
790     return;
791   }
792 
793   ProfilerParentTracker::Enumerate([](ProfilerParent* profilerParent) {
794     Unused << profilerParent->SendResumeSampling();
795   });
796 }
797 
798 /* static */
ClearAllPages()799 void ProfilerParent::ClearAllPages() {
800   if (!NS_IsMainThread()) {
801     return;
802   }
803 
804   ProfilerParentTracker::Enumerate([](ProfilerParent* profilerParent) {
805     Unused << profilerParent->SendClearAllPages();
806   });
807 }
808 
ActorDestroy(ActorDestroyReason aActorDestroyReason)809 void ProfilerParent::ActorDestroy(ActorDestroyReason aActorDestroyReason) {
810   MOZ_RELEASE_ASSERT(NS_IsMainThread());
811   mDestroyed = true;
812 }
813 
ActorDealloc()814 void ProfilerParent::ActorDealloc() { mSelfRef = nullptr; }
815 
816 #endif
817 
818 }  // namespace mozilla
819