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