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