diff options
Diffstat (limited to 'tools/profiler/gecko/ProfilerChild.cpp')
-rw-r--r-- | tools/profiler/gecko/ProfilerChild.cpp | 553 |
1 files changed, 553 insertions, 0 deletions
diff --git a/tools/profiler/gecko/ProfilerChild.cpp b/tools/profiler/gecko/ProfilerChild.cpp new file mode 100644 index 0000000000..24e63643d5 --- /dev/null +++ b/tools/profiler/gecko/ProfilerChild.cpp @@ -0,0 +1,553 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ProfilerChild.h" + +#include "GeckoProfiler.h" +#include "platform.h" +#include "ProfilerCodeAddressService.h" +#include "ProfilerControl.h" +#include "ProfilerParent.h" + +#include "chrome/common/ipc_channel.h" +#include "nsPrintfCString.h" +#include "nsThreadUtils.h" + +#include <memory> + +namespace mozilla { + +/* static */ DataMutexBase<ProfilerChild::ProfilerChildAndUpdate, + baseprofiler::detail::BaseProfilerMutex> + ProfilerChild::sPendingChunkManagerUpdate{ + "ProfilerChild::sPendingChunkManagerUpdate"}; + +ProfilerChild::ProfilerChild() + : mThread(NS_GetCurrentThread()), mDestroyed(false) { + MOZ_COUNT_CTOR(ProfilerChild); +} + +ProfilerChild::~ProfilerChild() { MOZ_COUNT_DTOR(ProfilerChild); } + +void ProfilerChild::ResolveChunkUpdate( + PProfilerChild::AwaitNextChunkManagerUpdateResolver& aResolve) { + MOZ_ASSERT(!!aResolve, + "ResolveChunkUpdate should only be called when there's a pending " + "resolver"); + MOZ_ASSERT( + !mChunkManagerUpdate.IsNotUpdate(), + "ResolveChunkUpdate should only be called with a real or final update"); + MOZ_ASSERT( + !mDestroyed, + "ResolveChunkUpdate should not be called if the actor was destroyed"); + if (mChunkManagerUpdate.IsFinal()) { + // Final update, send a special "unreleased value", but don't clear the + // local copy so we know we got the final update. + std::move(aResolve)(ProfilerParent::MakeFinalUpdate()); + } else { + // Optimization note: The ProfileBufferChunkManagerUpdate constructor takes + // the newly-released chunks nsTArray by reference-to-const, therefore + // constructing and then moving the array here would make a copy. So instead + // we first give it an empty array, and then we can write the data directly + // into the update's array. + ProfileBufferChunkManagerUpdate update{ + mChunkManagerUpdate.UnreleasedBytes(), + mChunkManagerUpdate.ReleasedBytes(), + mChunkManagerUpdate.OldestDoneTimeStamp(), + {}}; + update.newlyReleasedChunks().SetCapacity( + mChunkManagerUpdate.NewlyReleasedChunksRef().size()); + for (const ProfileBufferControlledChunkManager::ChunkMetadata& chunk : + mChunkManagerUpdate.NewlyReleasedChunksRef()) { + update.newlyReleasedChunks().EmplaceBack(chunk.mDoneTimeStamp, + chunk.mBufferBytes); + } + + std::move(aResolve)(update); + + // Clear the update we just sent, so it's ready for later updates to be + // folded into it. + mChunkManagerUpdate.Clear(); + } + + // Discard the resolver, so it's empty next time there's a new request. + aResolve = nullptr; +} + +void ProfilerChild::ProcessChunkManagerUpdate( + ProfileBufferControlledChunkManager::Update&& aUpdate) { + if (mDestroyed) { + return; + } + // Always store the data, it could be the final update. + mChunkManagerUpdate.Fold(std::move(aUpdate)); + if (mAwaitNextChunkManagerUpdateResolver) { + // There is already a pending resolver, give it the info now. + ResolveChunkUpdate(mAwaitNextChunkManagerUpdateResolver); + } +} + +/* static */ void ProfilerChild::ProcessPendingUpdate() { + auto lockedUpdate = sPendingChunkManagerUpdate.Lock(); + if (!lockedUpdate->mProfilerChild || lockedUpdate->mUpdate.IsNotUpdate()) { + return; + } + lockedUpdate->mProfilerChild->mThread->Dispatch(NS_NewRunnableFunction( + "ProfilerChild::ProcessPendingUpdate", []() mutable { + auto lockedUpdate = sPendingChunkManagerUpdate.Lock(); + if (!lockedUpdate->mProfilerChild || + lockedUpdate->mUpdate.IsNotUpdate()) { + return; + } + lockedUpdate->mProfilerChild->ProcessChunkManagerUpdate( + std::move(lockedUpdate->mUpdate)); + lockedUpdate->mUpdate.Clear(); + })); +} + +/* static */ bool ProfilerChild::IsLockedOnCurrentThread() { + return sPendingChunkManagerUpdate.Mutex().IsLockedOnCurrentThread(); +} + +void ProfilerChild::SetupChunkManager() { + mChunkManager = profiler_get_controlled_chunk_manager(); + if (NS_WARN_IF(!mChunkManager)) { + return; + } + + // Make sure there are no updates (from a previous run). + mChunkManagerUpdate.Clear(); + { + auto lockedUpdate = sPendingChunkManagerUpdate.Lock(); + lockedUpdate->mProfilerChild = this; + lockedUpdate->mUpdate.Clear(); + } + + mChunkManager->SetUpdateCallback( + [](ProfileBufferControlledChunkManager::Update&& aUpdate) { + // Updates from the chunk manager are stored for later processing. + // We avoid dispatching a task, as this could deadlock (if the queueing + // mutex is held elsewhere). + auto lockedUpdate = sPendingChunkManagerUpdate.Lock(); + if (!lockedUpdate->mProfilerChild) { + return; + } + lockedUpdate->mUpdate.Fold(std::move(aUpdate)); + }); +} + +void ProfilerChild::ResetChunkManager() { + if (!mChunkManager) { + return; + } + + // We have a chunk manager, reset the callback, which will add a final + // pending update. + mChunkManager->SetUpdateCallback({}); + + // Clear the pending update. + auto lockedUpdate = sPendingChunkManagerUpdate.Lock(); + lockedUpdate->mProfilerChild = nullptr; + lockedUpdate->mUpdate.Clear(); + // And process a final update right now. + ProcessChunkManagerUpdate( + ProfileBufferControlledChunkManager::Update(nullptr)); + + mChunkManager = nullptr; + mAwaitNextChunkManagerUpdateResolver = nullptr; +} + +mozilla::ipc::IPCResult ProfilerChild::RecvStart( + const ProfilerInitParams& params, StartResolver&& aResolve) { + nsTArray<const char*> filterArray; + for (size_t i = 0; i < params.filters().Length(); ++i) { + filterArray.AppendElement(params.filters()[i].get()); + } + + profiler_start(PowerOfTwo32(params.entries()), params.interval(), + params.features(), filterArray.Elements(), + filterArray.Length(), params.activeTabID(), params.duration()); + + SetupChunkManager(); + + aResolve(/* unused */ true); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ProfilerChild::RecvEnsureStarted( + const ProfilerInitParams& params, EnsureStartedResolver&& aResolve) { + nsTArray<const char*> filterArray; + for (size_t i = 0; i < params.filters().Length(); ++i) { + filterArray.AppendElement(params.filters()[i].get()); + } + + profiler_ensure_started(PowerOfTwo32(params.entries()), params.interval(), + params.features(), filterArray.Elements(), + filterArray.Length(), params.activeTabID(), + params.duration()); + + SetupChunkManager(); + + aResolve(/* unused */ true); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ProfilerChild::RecvStop(StopResolver&& aResolve) { + ResetChunkManager(); + profiler_stop(); + aResolve(/* unused */ true); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ProfilerChild::RecvPause(PauseResolver&& aResolve) { + profiler_pause(); + aResolve(/* unused */ true); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ProfilerChild::RecvResume(ResumeResolver&& aResolve) { + profiler_resume(); + aResolve(/* unused */ true); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ProfilerChild::RecvPauseSampling( + PauseSamplingResolver&& aResolve) { + profiler_pause_sampling(); + aResolve(/* unused */ true); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ProfilerChild::RecvResumeSampling( + ResumeSamplingResolver&& aResolve) { + profiler_resume_sampling(); + aResolve(/* unused */ true); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ProfilerChild::RecvWaitOnePeriodicSampling( + WaitOnePeriodicSamplingResolver&& aResolve) { + std::shared_ptr<WaitOnePeriodicSamplingResolver> resolve = + std::make_shared<WaitOnePeriodicSamplingResolver>(std::move(aResolve)); + if (!profiler_callback_after_sampling( + [self = RefPtr(this), resolve](SamplingState aSamplingState) mutable { + if (self->mDestroyed) { + return; + } + MOZ_RELEASE_ASSERT(self->mThread); + self->mThread->Dispatch(NS_NewRunnableFunction( + "nsProfiler::WaitOnePeriodicSampling result on main thread", + [resolve = std::move(resolve), aSamplingState]() { + (*resolve)(aSamplingState == + SamplingState::SamplingCompleted || + aSamplingState == + SamplingState::NoStackSamplingCompleted); + })); + })) { + // Callback was not added (e.g., profiler is not running) and will never be + // invoked, so we need to resolve the promise here. + (*resolve)(false); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ProfilerChild::RecvClearAllPages() { + profiler_clear_all_pages(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ProfilerChild::RecvAwaitNextChunkManagerUpdate( + AwaitNextChunkManagerUpdateResolver&& aResolve) { + MOZ_ASSERT(!mDestroyed, + "Recv... should not be called if the actor was destroyed"); + // Pick up pending updates if any. + { + auto lockedUpdate = sPendingChunkManagerUpdate.Lock(); + if (lockedUpdate->mProfilerChild && !lockedUpdate->mUpdate.IsNotUpdate()) { + mChunkManagerUpdate.Fold(std::move(lockedUpdate->mUpdate)); + lockedUpdate->mUpdate.Clear(); + } + } + if (mChunkManagerUpdate.IsNotUpdate()) { + // No data yet, store the resolver for later. + mAwaitNextChunkManagerUpdateResolver = std::move(aResolve); + } else { + // We have data, send it now. + ResolveChunkUpdate(aResolve); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ProfilerChild::RecvDestroyReleasedChunksAtOrBefore( + const TimeStamp& aTimeStamp) { + if (mChunkManager) { + mChunkManager->DestroyChunksAtOrBefore(aTimeStamp); + } + return IPC_OK(); +} + +struct GatherProfileThreadParameters + : public external::AtomicRefCounted<GatherProfileThreadParameters> { + MOZ_DECLARE_REFCOUNTED_TYPENAME(GatherProfileThreadParameters) + + GatherProfileThreadParameters( + RefPtr<ProfilerChild> aProfilerChild, + RefPtr<ProgressLogger::SharedProgress> aProgress, + ProfilerChild::GatherProfileResolver&& aResolver) + : profilerChild(std::move(aProfilerChild)), + progress(std::move(aProgress)), + resolver(std::move(aResolver)) {} + + RefPtr<ProfilerChild> profilerChild; + + FailureLatchSource failureLatchSource; + + // Separate RefPtr used when working on separate thread. This way, if the + // "ProfilerChild" thread decides to overwrite its mGatherProfileProgress with + // a new one, the work done here will still only use the old one. + RefPtr<ProgressLogger::SharedProgress> progress; + + // Resolver for the GatherProfile promise. Must only be called on the + // "ProfilerChild" thread. + ProfilerChild::GatherProfileResolver resolver; +}; + +/* static */ +void ProfilerChild::GatherProfileThreadFunction( + void* already_AddRefedParameters) { + PR_SetCurrentThreadName("GatherProfileThread"); + + RefPtr<GatherProfileThreadParameters> parameters = + already_AddRefed<GatherProfileThreadParameters>{ + static_cast<GatherProfileThreadParameters*>( + already_AddRefedParameters)}; + + ProgressLogger progressLogger( + parameters->progress, "Gather-profile thread started", "Profile sent"); + using namespace mozilla::literals::ProportionValue_literals; // For `1_pc`. + + auto writer = + MakeUnique<SpliceableChunkedJSONWriter>(parameters->failureLatchSource); + if (!profiler_get_profile_json( + *writer, + /* aSinceTime */ 0, + /* aIsShuttingDown */ false, + progressLogger.CreateSubLoggerFromTo( + 1_pc, "profiler_get_profile_json started", 99_pc, + "profiler_get_profile_json done"))) { + // Failed to get a profile, reset the writer pointer, so that we'll send a + // failure message. + writer.reset(); + } + + if (NS_WARN_IF(NS_FAILED( + parameters->profilerChild->mThread->Dispatch(NS_NewRunnableFunction( + "ProfilerChild::ProcessPendingUpdate", + [parameters, + // Forward progress logger to on-ProfilerChild-thread task, so + // that it doesn't get marked as 100% done when this off-thread + // function ends. + progressLogger = std::move(progressLogger), + writer = std::move(writer)]() mutable { + // We are now on the ProfilerChild thread, about to send the + // completed profile. Any incoming progress request will now be + // handled after this task ends, so updating the progress is now + // useless and we can just get rid of the progress storage. + if (parameters->profilerChild->mGatherProfileProgress == + parameters->progress) { + // The ProfilerChild progress is still the one we know. + parameters->profilerChild->mGatherProfileProgress = nullptr; + } + + // Shmem allocation and promise resolution must be made on the + // ProfilerChild thread, that's why this task was needed here. + mozilla::ipc::Shmem shmem; + if (writer) { + if (const size_t len = writer->ChunkedWriteFunc().Length(); + len < UINT32_MAX) { + bool shmemSuccess = true; + const bool copySuccess = + writer->ChunkedWriteFunc() + .CopyDataIntoLazilyAllocatedBuffer( + [&](size_t allocationSize) -> char* { + MOZ_ASSERT(allocationSize == len + 1); + if (parameters->profilerChild->AllocShmem( + allocationSize, &shmem)) { + return shmem.get<char>(); + } + shmemSuccess = false; + return nullptr; + }); + if (!shmemSuccess || !copySuccess) { + const nsPrintfCString message( + (!shmemSuccess) + ? "*Could not create shmem for profile from pid " + "%u (%zu B)" + : "*Could not write profile from pid %u (%zu B)", + unsigned(profiler_current_process_id().ToNumber()), + len); + if (parameters->profilerChild->AllocShmem( + message.Length() + 1, &shmem)) { + strcpy(shmem.get<char>(), message.Data()); + } + } + } else { + const nsPrintfCString message( + "*Profile from pid %u bigger (%zu) than shmem max " + "(%zu)", + unsigned(profiler_current_process_id().ToNumber()), len, + size_t(UINT32_MAX)); + if (parameters->profilerChild->AllocShmem( + message.Length() + 1, &shmem)) { + strcpy(shmem.get<char>(), message.Data()); + } + } + writer = nullptr; + } else { + // No profile. + const char* failure = + parameters->failureLatchSource.GetFailure(); + const nsPrintfCString message( + "*Could not generate profile from pid %u%s%s", + unsigned(profiler_current_process_id().ToNumber()), + failure ? ", failure: " : "", failure ? failure : ""); + if (parameters->profilerChild->AllocShmem( + message.Length() + 1, &shmem)) { + strcpy(shmem.get<char>(), message.Data()); + } + } + + parameters->resolver(std::move(shmem)); + }))))) { + // Failed to dispatch the task to the ProfilerChild thread. The IPC cannot + // be resolved on this thread, so it will never be resolved! + // And it would be unsafe to modify mGatherProfileProgress; But the parent + // should notice that's it's not advancing anymore. + } +} + +mozilla::ipc::IPCResult ProfilerChild::RecvGatherProfile( + GatherProfileResolver&& aResolve) { + mGatherProfileProgress = MakeRefPtr<ProgressLogger::SharedProgress>(); + mGatherProfileProgress->SetProgress(ProportionValue{0.0}, + "Received gather-profile request"); + + auto parameters = MakeRefPtr<GatherProfileThreadParameters>( + this, mGatherProfileProgress, std::move(aResolve)); + + // The GatherProfileThreadFunction thread function will cast its void* + // argument to already_AddRefed<GatherProfileThreadParameters>. + parameters.get()->AddRef(); + PRThread* gatherProfileThread = PR_CreateThread( + PR_SYSTEM_THREAD, GatherProfileThreadFunction, parameters.get(), + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_UNJOINABLE_THREAD, 0); + + if (!gatherProfileThread) { + // Failed to create and start worker thread, resolve with an empty profile. + mozilla::ipc::Shmem shmem; + if (AllocShmem(1, &shmem)) { + shmem.get<char>()[0] = '\0'; + } + parameters->resolver(std::move(shmem)); + // And clean up. + parameters.get()->Release(); + mGatherProfileProgress = nullptr; + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ProfilerChild::RecvGetGatherProfileProgress( + GetGatherProfileProgressResolver&& aResolve) { + if (mGatherProfileProgress) { + aResolve(GatherProfileProgress{ + mGatherProfileProgress->Progress().ToUnderlyingType(), + nsCString(mGatherProfileProgress->LastLocation())}); + } else { + aResolve( + GatherProfileProgress{ProportionValue::MakeInvalid().ToUnderlyingType(), + nsCString("No gather-profile in progress")}); + } + return IPC_OK(); +} + +void ProfilerChild::ActorDestroy(ActorDestroyReason aActorDestroyReason) { + mDestroyed = true; +} + +void ProfilerChild::Destroy() { + ResetChunkManager(); + if (!mDestroyed) { + Close(); + } +} + +nsCString ProfilerChild::GrabShutdownProfile() { + LOG("GrabShutdownProfile"); + + UniquePtr<ProfilerCodeAddressService> service = + profiler_code_address_service_for_presymbolication(); + FailureLatchSource failureLatch; + SpliceableChunkedJSONWriter writer{failureLatch}; + writer.Start(); + if (!profiler_stream_json_for_this_process(writer, /* aSinceTime */ 0, + /* aIsShuttingDown */ true, + service.get(), ProgressLogger{})) { + const char* failure = writer.GetFailure(); + return nsPrintfCString("*Profile unavailable for pid %u%s%s", + unsigned(profiler_current_process_id().ToNumber()), + failure ? ", failure: " : "", + failure ? failure : ""); + } + writer.StartArrayProperty("processes"); + writer.EndArray(); + writer.End(); + + const size_t len = writer.ChunkedWriteFunc().Length(); + // This string is destined to be sent as a shutdown profile, which is limited + // by the maximum IPC message size. + // TODO: IPC to change to shmem (bug 1780330), raising this limit to + // JS::MaxStringLength. + if (len >= size_t(IPC::Channel::kMaximumMessageSize)) { + return nsPrintfCString( + "*Profile from pid %u bigger (%zu) than IPC max (%zu)", + unsigned(profiler_current_process_id().ToNumber()), len, + size_t(IPC::Channel::kMaximumMessageSize)); + } + + nsCString profileCString; + if (!profileCString.SetLength(len, fallible)) { + return nsPrintfCString( + "*Could not allocate %zu bytes for profile from pid %u", len, + unsigned(profiler_current_process_id().ToNumber())); + } + MOZ_ASSERT(*(profileCString.Data() + len) == '\0', + "We expected a null at the end of the string buffer, to be " + "rewritten by CopyDataIntoLazilyAllocatedBuffer"); + + char* const profileBeginWriting = profileCString.BeginWriting(); + if (!profileBeginWriting) { + return nsPrintfCString("*Could not write profile from pid %u", + unsigned(profiler_current_process_id().ToNumber())); + } + + // Here, we have enough space reserved in `profileCString`, starting at + // `profileBeginWriting`, copy the JSON profile there. + if (!writer.ChunkedWriteFunc().CopyDataIntoLazilyAllocatedBuffer( + [&](size_t aBufferLen) -> char* { + MOZ_RELEASE_ASSERT(aBufferLen == len + 1); + return profileBeginWriting; + })) { + return nsPrintfCString("*Could not copy profile from pid %u", + unsigned(profiler_current_process_id().ToNumber())); + } + MOZ_ASSERT(*(profileCString.Data() + len) == '\0', + "We still expected a null at the end of the string buffer"); + + return profileCString; +} + +} // namespace mozilla |