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 "ProfilerChild.h"
8 
9 #include "GeckoProfiler.h"
10 #include "platform.h"
11 #include "ProfilerParent.h"
12 
13 #include "nsThreadUtils.h"
14 
15 namespace mozilla {
16 
17 /* static */ DataMutexBase<ProfilerChild::ProfilerChildAndUpdate,
18                            baseprofiler::detail::BaseProfilerMutex>
19     ProfilerChild::sPendingChunkManagerUpdate{
20         "ProfilerChild::sPendingChunkManagerUpdate"};
21 
ProfilerChild()22 ProfilerChild::ProfilerChild()
23     : mThread(NS_GetCurrentThread()), mDestroyed(false) {
24   MOZ_COUNT_CTOR(ProfilerChild);
25 }
26 
~ProfilerChild()27 ProfilerChild::~ProfilerChild() { MOZ_COUNT_DTOR(ProfilerChild); }
28 
ResolveChunkUpdate(PProfilerChild::AwaitNextChunkManagerUpdateResolver & aResolve)29 void ProfilerChild::ResolveChunkUpdate(
30     PProfilerChild::AwaitNextChunkManagerUpdateResolver& aResolve) {
31   MOZ_ASSERT(!!aResolve,
32              "ResolveChunkUpdate should only be called when there's a pending "
33              "resolver");
34   MOZ_ASSERT(
35       !mChunkManagerUpdate.IsNotUpdate(),
36       "ResolveChunkUpdate should only be called with a real or final update");
37   MOZ_ASSERT(
38       !mDestroyed,
39       "ResolveChunkUpdate should not be called if the actor was destroyed");
40   if (mChunkManagerUpdate.IsFinal()) {
41     // Final update, send a special "unreleased value", but don't clear the
42     // local copy so we know we got the final update.
43     std::move(aResolve)(ProfilerParent::MakeFinalUpdate());
44   } else {
45     // Optimization note: The ProfileBufferChunkManagerUpdate constructor takes
46     // the newly-released chunks nsTArray by reference-to-const, therefore
47     // constructing and then moving the array here would make a copy. So instead
48     // we first give it an empty array, and then we can write the data directly
49     // into the update's array.
50     ProfileBufferChunkManagerUpdate update{
51         mChunkManagerUpdate.UnreleasedBytes(),
52         mChunkManagerUpdate.ReleasedBytes(),
53         mChunkManagerUpdate.OldestDoneTimeStamp(),
54         {}};
55     update.newlyReleasedChunks().SetCapacity(
56         mChunkManagerUpdate.NewlyReleasedChunksRef().size());
57     for (const ProfileBufferControlledChunkManager::ChunkMetadata& chunk :
58          mChunkManagerUpdate.NewlyReleasedChunksRef()) {
59       update.newlyReleasedChunks().EmplaceBack(chunk.mDoneTimeStamp,
60                                                chunk.mBufferBytes);
61     }
62 
63     std::move(aResolve)(update);
64 
65     // Clear the update we just sent, so it's ready for later updates to be
66     // folded into it.
67     mChunkManagerUpdate.Clear();
68   }
69 
70   // Discard the resolver, so it's empty next time there's a new request.
71   aResolve = nullptr;
72 }
73 
ProcessChunkManagerUpdate(ProfileBufferControlledChunkManager::Update && aUpdate)74 void ProfilerChild::ProcessChunkManagerUpdate(
75     ProfileBufferControlledChunkManager::Update&& aUpdate) {
76   if (mDestroyed) {
77     return;
78   }
79   // Always store the data, it could be the final update.
80   mChunkManagerUpdate.Fold(std::move(aUpdate));
81   if (mAwaitNextChunkManagerUpdateResolver) {
82     // There is already a pending resolver, give it the info now.
83     ResolveChunkUpdate(mAwaitNextChunkManagerUpdateResolver);
84   }
85 }
86 
ProcessPendingUpdate()87 /* static */ void ProfilerChild::ProcessPendingUpdate() {
88   auto lockedUpdate = sPendingChunkManagerUpdate.Lock();
89   if (!lockedUpdate->mProfilerChild || lockedUpdate->mUpdate.IsNotUpdate()) {
90     return;
91   }
92   lockedUpdate->mProfilerChild->mThread->Dispatch(NS_NewRunnableFunction(
93       "ProfilerChild::ProcessPendingUpdate", []() mutable {
94         auto lockedUpdate = sPendingChunkManagerUpdate.Lock();
95         if (!lockedUpdate->mProfilerChild ||
96             lockedUpdate->mUpdate.IsNotUpdate()) {
97           return;
98         }
99         lockedUpdate->mProfilerChild->ProcessChunkManagerUpdate(
100             std::move(lockedUpdate->mUpdate));
101         lockedUpdate->mUpdate.Clear();
102       }));
103 }
104 
IsLockedOnCurrentThread()105 /* static */ bool ProfilerChild::IsLockedOnCurrentThread() {
106   return sPendingChunkManagerUpdate.Mutex().IsLockedOnCurrentThread();
107 }
108 
SetupChunkManager()109 void ProfilerChild::SetupChunkManager() {
110   mChunkManager = profiler_get_controlled_chunk_manager();
111   if (NS_WARN_IF(!mChunkManager)) {
112     return;
113   }
114 
115   // Make sure there are no updates (from a previous run).
116   mChunkManagerUpdate.Clear();
117   {
118     auto lockedUpdate = sPendingChunkManagerUpdate.Lock();
119     lockedUpdate->mProfilerChild = this;
120     lockedUpdate->mUpdate.Clear();
121   }
122 
123   mChunkManager->SetUpdateCallback(
124       [](ProfileBufferControlledChunkManager::Update&& aUpdate) {
125         // Updates from the chunk manager are stored for later processing.
126         // We avoid dispatching a task, as this could deadlock (if the queueing
127         // mutex is held elsewhere).
128         auto lockedUpdate = sPendingChunkManagerUpdate.Lock();
129         if (!lockedUpdate->mProfilerChild) {
130           return;
131         }
132         lockedUpdate->mUpdate.Fold(std::move(aUpdate));
133       });
134 }
135 
ResetChunkManager()136 void ProfilerChild::ResetChunkManager() {
137   if (!mChunkManager) {
138     return;
139   }
140 
141   // We have a chunk manager, reset the callback, which will add a final
142   // pending update.
143   mChunkManager->SetUpdateCallback({});
144 
145   // Clear the pending update.
146   auto lockedUpdate = sPendingChunkManagerUpdate.Lock();
147   lockedUpdate->mProfilerChild = nullptr;
148   lockedUpdate->mUpdate.Clear();
149   // And process a final update right now.
150   ProcessChunkManagerUpdate(
151       ProfileBufferControlledChunkManager::Update(nullptr));
152 
153   mChunkManager = nullptr;
154   mAwaitNextChunkManagerUpdateResolver = nullptr;
155 }
156 
RecvStart(const ProfilerInitParams & params)157 mozilla::ipc::IPCResult ProfilerChild::RecvStart(
158     const ProfilerInitParams& params) {
159   nsTArray<const char*> filterArray;
160   for (size_t i = 0; i < params.filters().Length(); ++i) {
161     filterArray.AppendElement(params.filters()[i].get());
162   }
163 
164   profiler_start(PowerOfTwo32(params.entries()), params.interval(),
165                  params.features(), filterArray.Elements(),
166                  filterArray.Length(), params.activeTabID(), params.duration());
167 
168   SetupChunkManager();
169 
170   return IPC_OK();
171 }
172 
RecvEnsureStarted(const ProfilerInitParams & params)173 mozilla::ipc::IPCResult ProfilerChild::RecvEnsureStarted(
174     const ProfilerInitParams& params) {
175   nsTArray<const char*> filterArray;
176   for (size_t i = 0; i < params.filters().Length(); ++i) {
177     filterArray.AppendElement(params.filters()[i].get());
178   }
179 
180   profiler_ensure_started(PowerOfTwo32(params.entries()), params.interval(),
181                           params.features(), filterArray.Elements(),
182                           filterArray.Length(), params.activeTabID(),
183                           params.duration());
184 
185   SetupChunkManager();
186 
187   return IPC_OK();
188 }
189 
RecvStop()190 mozilla::ipc::IPCResult ProfilerChild::RecvStop() {
191   ResetChunkManager();
192   profiler_stop();
193   return IPC_OK();
194 }
195 
RecvPause()196 mozilla::ipc::IPCResult ProfilerChild::RecvPause() {
197   profiler_pause();
198   return IPC_OK();
199 }
200 
RecvResume()201 mozilla::ipc::IPCResult ProfilerChild::RecvResume() {
202   profiler_resume();
203   return IPC_OK();
204 }
205 
RecvPauseSampling()206 mozilla::ipc::IPCResult ProfilerChild::RecvPauseSampling() {
207   profiler_pause_sampling();
208   return IPC_OK();
209 }
210 
RecvResumeSampling()211 mozilla::ipc::IPCResult ProfilerChild::RecvResumeSampling() {
212   profiler_resume_sampling();
213   return IPC_OK();
214 }
215 
RecvClearAllPages()216 mozilla::ipc::IPCResult ProfilerChild::RecvClearAllPages() {
217   profiler_clear_all_pages();
218   return IPC_OK();
219 }
220 
CollectProfileOrEmptyString(bool aIsShuttingDown)221 static nsCString CollectProfileOrEmptyString(bool aIsShuttingDown) {
222   nsCString profileCString;
223   UniquePtr<char[]> profile =
224       profiler_get_profile(/* aSinceTime */ 0, aIsShuttingDown);
225   if (profile) {
226     size_t len = strlen(profile.get());
227     profileCString.Adopt(profile.release(), len);
228   }
229   return profileCString;
230 }
231 
RecvAwaitNextChunkManagerUpdate(AwaitNextChunkManagerUpdateResolver && aResolve)232 mozilla::ipc::IPCResult ProfilerChild::RecvAwaitNextChunkManagerUpdate(
233     AwaitNextChunkManagerUpdateResolver&& aResolve) {
234   MOZ_ASSERT(!mDestroyed,
235              "Recv... should not be called if the actor was destroyed");
236   // Pick up pending updates if any.
237   {
238     auto lockedUpdate = sPendingChunkManagerUpdate.Lock();
239     if (lockedUpdate->mProfilerChild && !lockedUpdate->mUpdate.IsNotUpdate()) {
240       mChunkManagerUpdate.Fold(std::move(lockedUpdate->mUpdate));
241       lockedUpdate->mUpdate.Clear();
242     }
243   }
244   if (mChunkManagerUpdate.IsNotUpdate()) {
245     // No data yet, store the resolver for later.
246     mAwaitNextChunkManagerUpdateResolver = std::move(aResolve);
247   } else {
248     // We have data, send it now.
249     ResolveChunkUpdate(aResolve);
250   }
251   return IPC_OK();
252 }
253 
RecvDestroyReleasedChunksAtOrBefore(const TimeStamp & aTimeStamp)254 mozilla::ipc::IPCResult ProfilerChild::RecvDestroyReleasedChunksAtOrBefore(
255     const TimeStamp& aTimeStamp) {
256   if (mChunkManager) {
257     mChunkManager->DestroyChunksAtOrBefore(aTimeStamp);
258   }
259   return IPC_OK();
260 }
261 
RecvGatherProfile(GatherProfileResolver && aResolve)262 mozilla::ipc::IPCResult ProfilerChild::RecvGatherProfile(
263     GatherProfileResolver&& aResolve) {
264   mozilla::ipc::Shmem shmem;
265   profiler_get_profile_json_into_lazily_allocated_buffer(
266       [&](size_t allocationSize) -> char* {
267         if (AllocShmem(allocationSize,
268                        mozilla::ipc::Shmem::SharedMemory::TYPE_BASIC, &shmem)) {
269           return shmem.get<char>();
270         }
271         return nullptr;
272       },
273       /* aSinceTime */ 0,
274       /* aIsShuttingDown */ false);
275   aResolve(std::move(shmem));
276   return IPC_OK();
277 }
278 
ActorDestroy(ActorDestroyReason aActorDestroyReason)279 void ProfilerChild::ActorDestroy(ActorDestroyReason aActorDestroyReason) {
280   mDestroyed = true;
281 }
282 
Destroy()283 void ProfilerChild::Destroy() {
284   ResetChunkManager();
285   if (!mDestroyed) {
286     Close();
287   }
288 }
289 
GrabShutdownProfile()290 nsCString ProfilerChild::GrabShutdownProfile() {
291   return CollectProfileOrEmptyString(/* aIsShuttingDown */ true);
292 }
293 
294 }  // namespace mozilla
295