1004 lines
34 KiB
C++
1004 lines
34 KiB
C++
/* -*- 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 "ProfilerParent.h"
|
|
|
|
#ifdef MOZ_GECKO_PROFILER
|
|
# include "nsProfiler.h"
|
|
# include "platform.h"
|
|
#endif
|
|
|
|
#include "GeckoProfiler.h"
|
|
#include "ProfilerControl.h"
|
|
#include "mozilla/BaseAndGeckoProfilerDetail.h"
|
|
#include "mozilla/BaseProfilerDetail.h"
|
|
#include "mozilla/ClearOnShutdown.h"
|
|
#include "mozilla/DataMutex.h"
|
|
#include "mozilla/IOInterposer.h"
|
|
#include "mozilla/ipc/Endpoint.h"
|
|
#include "mozilla/Maybe.h"
|
|
#include "mozilla/ProfileBufferControlledChunkManager.h"
|
|
#include "mozilla/ProfilerBufferSize.h"
|
|
#include "mozilla/RefPtr.h"
|
|
#include "mozilla/Unused.h"
|
|
#include "nsTArray.h"
|
|
#include "nsThreadUtils.h"
|
|
|
|
#include <utility>
|
|
|
|
namespace mozilla {
|
|
|
|
using namespace ipc;
|
|
|
|
/* static */
|
|
Endpoint<PProfilerChild> ProfilerParent::CreateForProcess(
|
|
base::ProcessId aOtherPid) {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
Endpoint<PProfilerChild> child;
|
|
#ifdef MOZ_GECKO_PROFILER
|
|
Endpoint<PProfilerParent> parent;
|
|
nsresult rv = PProfiler::CreateEndpoints(&parent, &child);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
MOZ_CRASH("Failed to create top level actor for PProfiler!");
|
|
}
|
|
|
|
RefPtr<ProfilerParent> actor = new ProfilerParent(aOtherPid);
|
|
if (!parent.Bind(actor)) {
|
|
MOZ_CRASH("Failed to bind parent actor for PProfiler!");
|
|
}
|
|
|
|
actor->Init();
|
|
#endif
|
|
|
|
return child;
|
|
}
|
|
|
|
#ifdef MOZ_GECKO_PROFILER
|
|
|
|
class ProfilerParentTracker;
|
|
|
|
// This class is responsible for gathering updates from chunk managers in
|
|
// different process, and request for the oldest chunks to be destroyed whenever
|
|
// the given memory limit is reached.
|
|
class ProfileBufferGlobalController final {
|
|
public:
|
|
explicit ProfileBufferGlobalController(size_t aMaximumBytes);
|
|
|
|
~ProfileBufferGlobalController();
|
|
|
|
void HandleChildChunkManagerUpdate(
|
|
base::ProcessId aProcessId,
|
|
ProfileBufferControlledChunkManager::Update&& aUpdate);
|
|
|
|
static bool IsLockedOnCurrentThread();
|
|
|
|
private:
|
|
// Calls aF(Json::Value&).
|
|
template <typename F>
|
|
void Log(F&& aF);
|
|
|
|
static void LogUpdateChunks(Json::Value& updates, base::ProcessId aProcessId,
|
|
const TimeStamp& aTimeStamp, int aChunkDiff);
|
|
void LogUpdate(base::ProcessId aProcessId,
|
|
const ProfileBufferControlledChunkManager::Update& aUpdate);
|
|
void LogDeletion(base::ProcessId aProcessId, const TimeStamp& aTimeStamp);
|
|
|
|
void HandleChunkManagerNonFinalUpdate(
|
|
base::ProcessId aProcessId,
|
|
ProfileBufferControlledChunkManager::Update&& aUpdate,
|
|
ProfileBufferControlledChunkManager& aParentChunkManager);
|
|
|
|
const size_t mMaximumBytes;
|
|
|
|
const base::ProcessId mParentProcessId = base::GetCurrentProcId();
|
|
|
|
struct ParentChunkManagerAndPendingUpdate {
|
|
ProfileBufferControlledChunkManager* mChunkManager = nullptr;
|
|
ProfileBufferControlledChunkManager::Update mPendingUpdate;
|
|
};
|
|
|
|
static DataMutexBase<ParentChunkManagerAndPendingUpdate,
|
|
baseprofiler::detail::BaseProfilerMutex>
|
|
sParentChunkManagerAndPendingUpdate;
|
|
|
|
size_t mUnreleasedTotalBytes = 0;
|
|
|
|
struct PidAndBytes {
|
|
base::ProcessId mProcessId;
|
|
size_t mBytes;
|
|
|
|
// For searching and sorting.
|
|
bool operator==(base::ProcessId aSearchedProcessId) const {
|
|
return mProcessId == aSearchedProcessId;
|
|
}
|
|
bool operator==(const PidAndBytes& aOther) const {
|
|
return mProcessId == aOther.mProcessId;
|
|
}
|
|
bool operator<(base::ProcessId aSearchedProcessId) const {
|
|
return mProcessId < aSearchedProcessId;
|
|
}
|
|
bool operator<(const PidAndBytes& aOther) const {
|
|
return mProcessId < aOther.mProcessId;
|
|
}
|
|
};
|
|
using PidAndBytesArray = nsTArray<PidAndBytes>;
|
|
PidAndBytesArray mUnreleasedBytesByPid;
|
|
|
|
size_t mReleasedTotalBytes = 0;
|
|
|
|
struct TimeStampAndBytesAndPid {
|
|
TimeStamp mTimeStamp;
|
|
size_t mBytes;
|
|
base::ProcessId mProcessId;
|
|
|
|
// For searching and sorting.
|
|
bool operator==(const TimeStampAndBytesAndPid& aOther) const {
|
|
// Sort first by timestamps, and then by pid in rare cases with the same
|
|
// timestamps.
|
|
return mTimeStamp == aOther.mTimeStamp && mProcessId == aOther.mProcessId;
|
|
}
|
|
bool operator<(const TimeStampAndBytesAndPid& aOther) const {
|
|
// Sort first by timestamps, and then by pid in rare cases with the same
|
|
// timestamps.
|
|
return mTimeStamp < aOther.mTimeStamp ||
|
|
(MOZ_UNLIKELY(mTimeStamp == aOther.mTimeStamp) &&
|
|
mProcessId < aOther.mProcessId);
|
|
}
|
|
};
|
|
using TimeStampAndBytesAndPidArray = nsTArray<TimeStampAndBytesAndPid>;
|
|
TimeStampAndBytesAndPidArray mReleasedChunksByTime;
|
|
};
|
|
|
|
/* static */
|
|
MOZ_RUNINIT DataMutexBase<
|
|
ProfileBufferGlobalController::ParentChunkManagerAndPendingUpdate,
|
|
baseprofiler::detail::BaseProfilerMutex>
|
|
ProfileBufferGlobalController::sParentChunkManagerAndPendingUpdate{
|
|
"ProfileBufferGlobalController::sParentChunkManagerAndPendingUpdate"};
|
|
|
|
// This singleton class tracks live ProfilerParent's (meaning there's a current
|
|
// connection with a child process).
|
|
// It also knows when the local profiler is running.
|
|
// And when both the profiler is running and at least one child is present, it
|
|
// creates a ProfileBufferGlobalController and forwards chunk updates to it.
|
|
class ProfilerParentTracker final {
|
|
public:
|
|
static void StartTracking(ProfilerParent* aParent);
|
|
static void StopTracking(ProfilerParent* aParent);
|
|
|
|
static void ProfilerStarted(uint32_t aEntries);
|
|
static void ProfilerWillStopIfStarted();
|
|
|
|
// Number of non-destroyed tracked ProfilerParents.
|
|
static size_t ProfilerParentCount();
|
|
|
|
template <typename FuncType>
|
|
static void Enumerate(FuncType&& aIterFunc);
|
|
|
|
template <typename FuncType>
|
|
static void ForChild(base::ProcessId aChildPid, FuncType&& aIterFunc);
|
|
|
|
static void ForwardChildChunkManagerUpdate(
|
|
base::ProcessId aProcessId,
|
|
ProfileBufferControlledChunkManager::Update&& aUpdate);
|
|
|
|
ProfilerParentTracker();
|
|
~ProfilerParentTracker();
|
|
|
|
private:
|
|
// Get the singleton instance; Create one on the first request, unless we are
|
|
// past XPCOMShutdownThreads, which is when it should get destroyed.
|
|
static ProfilerParentTracker* GetInstance();
|
|
|
|
// List of parents for currently-connected child processes.
|
|
nsTArray<ProfilerParent*> mProfilerParents;
|
|
|
|
// If non-0, the parent profiler is running, with this limit (in number of
|
|
// entries.) This is needed here, because the parent profiler may start
|
|
// running before child processes are known (e.g., startup profiling).
|
|
uint32_t mEntries = 0;
|
|
|
|
// When the profiler is running and there is at least one parent-child
|
|
// connection, this is the controller that should receive chunk updates.
|
|
Maybe<ProfileBufferGlobalController> mMaybeController;
|
|
};
|
|
|
|
template <typename F>
|
|
void ProfileBufferGlobalController::Log(F&& aF) {
|
|
ProfilingLog::Access([&](Json::Value& aLog) {
|
|
Json::Value& root = aLog[Json::StaticString{"bufferGlobalController"}];
|
|
if (!root.isObject()) {
|
|
root = Json::Value(Json::objectValue);
|
|
root[Json::StaticString{"logBegin" TIMESTAMP_JSON_SUFFIX}] =
|
|
ProfilingLog::Timestamp();
|
|
}
|
|
std::forward<F>(aF)(root);
|
|
});
|
|
}
|
|
|
|
/* static */
|
|
void ProfileBufferGlobalController::LogUpdateChunks(Json::Value& updates,
|
|
base::ProcessId aProcessId,
|
|
const TimeStamp& aTimeStamp,
|
|
int aChunkDiff) {
|
|
MOZ_ASSERT(updates.isArray());
|
|
Json::Value row{Json::arrayValue};
|
|
row.append(Json::Value{Json::UInt64(aProcessId)});
|
|
row.append(ProfilingLog::Timestamp(aTimeStamp));
|
|
row.append(Json::Value{Json::Int(aChunkDiff)});
|
|
updates.append(std::move(row));
|
|
}
|
|
|
|
void ProfileBufferGlobalController::LogUpdate(
|
|
base::ProcessId aProcessId,
|
|
const ProfileBufferControlledChunkManager::Update& aUpdate) {
|
|
Log([&](Json::Value& aRoot) {
|
|
Json::Value& updates = aRoot[Json::StaticString{"updates"}];
|
|
if (!updates.isArray()) {
|
|
aRoot[Json::StaticString{"updatesSchema"}] =
|
|
Json::StaticString{"0: pid, 1: chunkRelease_TSms, 3: chunkDiff"};
|
|
updates = Json::Value{Json::arrayValue};
|
|
}
|
|
if (aUpdate.IsFinal()) {
|
|
LogUpdateChunks(updates, aProcessId, TimeStamp{}, 0);
|
|
} else if (!aUpdate.IsNotUpdate()) {
|
|
for (const auto& chunk : aUpdate.NewlyReleasedChunksRef()) {
|
|
LogUpdateChunks(updates, aProcessId, chunk.mDoneTimeStamp, 1);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void ProfileBufferGlobalController::LogDeletion(base::ProcessId aProcessId,
|
|
const TimeStamp& aTimeStamp) {
|
|
Log([&](Json::Value& aRoot) {
|
|
Json::Value& updates = aRoot[Json::StaticString{"updates"}];
|
|
if (!updates.isArray()) {
|
|
updates = Json::Value{Json::arrayValue};
|
|
}
|
|
LogUpdateChunks(updates, aProcessId, aTimeStamp, -1);
|
|
});
|
|
}
|
|
|
|
ProfileBufferGlobalController::ProfileBufferGlobalController(
|
|
size_t aMaximumBytes)
|
|
: mMaximumBytes(aMaximumBytes) {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
Log([](Json::Value& aRoot) {
|
|
aRoot[Json::StaticString{"controllerCreationTime" TIMESTAMP_JSON_SUFFIX}] =
|
|
ProfilingLog::Timestamp();
|
|
});
|
|
|
|
// This is the local chunk manager for this parent process, so updates can be
|
|
// handled here.
|
|
ProfileBufferControlledChunkManager* parentChunkManager =
|
|
profiler_get_controlled_chunk_manager();
|
|
|
|
if (NS_WARN_IF(!parentChunkManager)) {
|
|
Log([](Json::Value& aRoot) {
|
|
aRoot[Json::StaticString{"controllerCreationFailureReason"}] =
|
|
"No parent chunk manager";
|
|
});
|
|
return;
|
|
}
|
|
|
|
{
|
|
auto lockedParentChunkManagerAndPendingUpdate =
|
|
sParentChunkManagerAndPendingUpdate.Lock();
|
|
lockedParentChunkManagerAndPendingUpdate->mChunkManager =
|
|
parentChunkManager;
|
|
}
|
|
|
|
parentChunkManager->SetUpdateCallback(
|
|
[this](ProfileBufferControlledChunkManager::Update&& aUpdate) {
|
|
MOZ_ASSERT(!aUpdate.IsNotUpdate(),
|
|
"Update callback should never be given a non-update");
|
|
auto lockedParentChunkManagerAndPendingUpdate =
|
|
sParentChunkManagerAndPendingUpdate.Lock();
|
|
if (aUpdate.IsFinal()) {
|
|
// Final update of the parent.
|
|
// We cannot keep the chunk manager, and there's no point handling
|
|
// updates anymore. Do some cleanup now, to free resources before
|
|
// we're destroyed.
|
|
lockedParentChunkManagerAndPendingUpdate->mChunkManager = nullptr;
|
|
lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.Clear();
|
|
mUnreleasedTotalBytes = 0;
|
|
mUnreleasedBytesByPid.Clear();
|
|
mReleasedTotalBytes = 0;
|
|
mReleasedChunksByTime.Clear();
|
|
return;
|
|
}
|
|
if (!lockedParentChunkManagerAndPendingUpdate->mChunkManager) {
|
|
// No chunk manager, ignore updates.
|
|
return;
|
|
}
|
|
// Special handling of parent non-final updates:
|
|
// These updates are coming from *this* process, and may originate from
|
|
// scopes in any thread where any lock is held, so using other locks (to
|
|
// e.g., dispatch tasks or send IPCs) could trigger a deadlock. Instead,
|
|
// parent updates are stored locally and handled when the next
|
|
// non-parent update needs handling, see HandleChildChunkManagerUpdate.
|
|
lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.Fold(
|
|
std::move(aUpdate));
|
|
});
|
|
}
|
|
|
|
ProfileBufferGlobalController ::~ProfileBufferGlobalController() {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
// Extract the parent chunk manager (if still set).
|
|
// This means any update after this will be ignored.
|
|
ProfileBufferControlledChunkManager* parentChunkManager = []() {
|
|
auto lockedParentChunkManagerAndPendingUpdate =
|
|
sParentChunkManagerAndPendingUpdate.Lock();
|
|
lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.Clear();
|
|
return std::exchange(
|
|
lockedParentChunkManagerAndPendingUpdate->mChunkManager, nullptr);
|
|
}();
|
|
if (parentChunkManager) {
|
|
// We had not received a final update yet, so the chunk manager is still
|
|
// valid. Reset the callback in the chunk manager, this will immediately
|
|
// invoke the callback with the final empty update; see handling above.
|
|
parentChunkManager->SetUpdateCallback({});
|
|
}
|
|
}
|
|
|
|
void ProfileBufferGlobalController::HandleChildChunkManagerUpdate(
|
|
base::ProcessId aProcessId,
|
|
ProfileBufferControlledChunkManager::Update&& aUpdate) {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
MOZ_ASSERT(aProcessId != mParentProcessId);
|
|
|
|
MOZ_ASSERT(!aUpdate.IsNotUpdate(),
|
|
"HandleChildChunkManagerUpdate should not be given a non-update");
|
|
|
|
auto lockedParentChunkManagerAndPendingUpdate =
|
|
sParentChunkManagerAndPendingUpdate.Lock();
|
|
if (!lockedParentChunkManagerAndPendingUpdate->mChunkManager) {
|
|
// No chunk manager, ignore updates.
|
|
return;
|
|
}
|
|
|
|
if (aUpdate.IsFinal()) {
|
|
// Final update in a child process, remove all traces of that process.
|
|
LogUpdate(aProcessId, aUpdate);
|
|
size_t index = mUnreleasedBytesByPid.BinaryIndexOf(aProcessId);
|
|
if (index != PidAndBytesArray::NoIndex) {
|
|
// We already have a value for this pid.
|
|
PidAndBytes& pidAndBytes = mUnreleasedBytesByPid[index];
|
|
mUnreleasedTotalBytes -= pidAndBytes.mBytes;
|
|
mUnreleasedBytesByPid.RemoveElementAt(index);
|
|
}
|
|
|
|
size_t released = 0;
|
|
mReleasedChunksByTime.RemoveElementsBy(
|
|
[&released, aProcessId](const auto& chunk) {
|
|
const bool match = chunk.mProcessId == aProcessId;
|
|
if (match) {
|
|
released += chunk.mBytes;
|
|
}
|
|
return match;
|
|
});
|
|
if (released != 0) {
|
|
mReleasedTotalBytes -= released;
|
|
}
|
|
|
|
// Total can only have gone down, so there's no need to check the limit.
|
|
return;
|
|
}
|
|
|
|
// Non-final update in child process.
|
|
|
|
// Before handling the child update, we may have pending updates from the
|
|
// parent, which can be processed now since we're in an IPC callback outside
|
|
// of any profiler-related scope.
|
|
if (!lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.IsNotUpdate()) {
|
|
MOZ_ASSERT(
|
|
!lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.IsFinal());
|
|
HandleChunkManagerNonFinalUpdate(
|
|
mParentProcessId,
|
|
std::move(lockedParentChunkManagerAndPendingUpdate->mPendingUpdate),
|
|
*lockedParentChunkManagerAndPendingUpdate->mChunkManager);
|
|
lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.Clear();
|
|
}
|
|
|
|
HandleChunkManagerNonFinalUpdate(
|
|
aProcessId, std::move(aUpdate),
|
|
*lockedParentChunkManagerAndPendingUpdate->mChunkManager);
|
|
}
|
|
|
|
/* static */
|
|
bool ProfileBufferGlobalController::IsLockedOnCurrentThread() {
|
|
return sParentChunkManagerAndPendingUpdate.Mutex().IsLockedOnCurrentThread();
|
|
}
|
|
|
|
void ProfileBufferGlobalController::HandleChunkManagerNonFinalUpdate(
|
|
base::ProcessId aProcessId,
|
|
ProfileBufferControlledChunkManager::Update&& aUpdate,
|
|
ProfileBufferControlledChunkManager& aParentChunkManager) {
|
|
MOZ_ASSERT(!aUpdate.IsFinal());
|
|
LogUpdate(aProcessId, aUpdate);
|
|
|
|
size_t index = mUnreleasedBytesByPid.BinaryIndexOf(aProcessId);
|
|
if (index != PidAndBytesArray::NoIndex) {
|
|
// We already have a value for this pid.
|
|
PidAndBytes& pidAndBytes = mUnreleasedBytesByPid[index];
|
|
mUnreleasedTotalBytes =
|
|
mUnreleasedTotalBytes - pidAndBytes.mBytes + aUpdate.UnreleasedBytes();
|
|
pidAndBytes.mBytes = aUpdate.UnreleasedBytes();
|
|
} else {
|
|
// New pid.
|
|
mUnreleasedBytesByPid.InsertElementSorted(
|
|
PidAndBytes{aProcessId, aUpdate.UnreleasedBytes()});
|
|
mUnreleasedTotalBytes += aUpdate.UnreleasedBytes();
|
|
}
|
|
|
|
size_t destroyedReleased = 0;
|
|
if (!aUpdate.OldestDoneTimeStamp().IsNull()) {
|
|
size_t i = 0;
|
|
for (; i < mReleasedChunksByTime.Length(); ++i) {
|
|
if (mReleasedChunksByTime[i].mTimeStamp >=
|
|
aUpdate.OldestDoneTimeStamp()) {
|
|
break;
|
|
}
|
|
}
|
|
// Here, i is the index of the first item that's at or after
|
|
// aUpdate.mOldestDoneTimeStamp, so chunks from aProcessId before that have
|
|
// been destroyed.
|
|
while (i != 0) {
|
|
--i;
|
|
const TimeStampAndBytesAndPid& item = mReleasedChunksByTime[i];
|
|
if (item.mProcessId == aProcessId) {
|
|
destroyedReleased += item.mBytes;
|
|
mReleasedChunksByTime.RemoveElementAt(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
size_t newlyReleased = 0;
|
|
for (const ProfileBufferControlledChunkManager::ChunkMetadata& chunk :
|
|
aUpdate.NewlyReleasedChunksRef()) {
|
|
newlyReleased += chunk.mBufferBytes;
|
|
mReleasedChunksByTime.InsertElementSorted(TimeStampAndBytesAndPid{
|
|
chunk.mDoneTimeStamp, chunk.mBufferBytes, aProcessId});
|
|
}
|
|
|
|
mReleasedTotalBytes = mReleasedTotalBytes - destroyedReleased + newlyReleased;
|
|
|
|
# ifdef DEBUG
|
|
size_t totalReleased = 0;
|
|
for (const TimeStampAndBytesAndPid& item : mReleasedChunksByTime) {
|
|
totalReleased += item.mBytes;
|
|
}
|
|
MOZ_ASSERT(mReleasedTotalBytes == totalReleased);
|
|
# endif // DEBUG
|
|
|
|
std::vector<ProfileBufferControlledChunkManager::ChunkMetadata> toDestroy;
|
|
while (mUnreleasedTotalBytes + mReleasedTotalBytes > mMaximumBytes &&
|
|
!mReleasedChunksByTime.IsEmpty()) {
|
|
// We have reached the global memory limit, and there *are* released chunks
|
|
// that can be destroyed. Start with the first one, which is the oldest.
|
|
const TimeStampAndBytesAndPid& oldest = mReleasedChunksByTime[0];
|
|
LogDeletion(oldest.mProcessId, oldest.mTimeStamp);
|
|
mReleasedTotalBytes -= oldest.mBytes;
|
|
if (oldest.mProcessId == mParentProcessId) {
|
|
aParentChunkManager.DestroyChunksAtOrBefore(oldest.mTimeStamp);
|
|
} else {
|
|
ProfilerParentTracker::ForChild(
|
|
oldest.mProcessId,
|
|
[timestamp = oldest.mTimeStamp](ProfilerParent* profilerParent) {
|
|
Unused << profilerParent->SendDestroyReleasedChunksAtOrBefore(
|
|
timestamp);
|
|
});
|
|
}
|
|
mReleasedChunksByTime.RemoveElementAt(0);
|
|
}
|
|
}
|
|
|
|
/* static */
|
|
ProfilerParentTracker* ProfilerParentTracker::GetInstance() {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
// The main instance pointer, it will be initialized at most once, before
|
|
// XPCOMShutdownThreads.
|
|
static StaticAutoPtr<ProfilerParentTracker> instance;
|
|
if (MOZ_UNLIKELY(!instance)) {
|
|
if (PastShutdownPhase(ShutdownPhase::XPCOMShutdownThreads)) {
|
|
return nullptr;
|
|
}
|
|
|
|
instance = new ProfilerParentTracker();
|
|
|
|
// The tracker should get destroyed before threads are shutdown, because its
|
|
// destruction closes extant channels, which could trigger promise
|
|
// rejections that need to be dispatched to other threads.
|
|
ClearOnShutdown(&instance, ShutdownPhase::XPCOMShutdownThreads);
|
|
}
|
|
|
|
return instance.get();
|
|
}
|
|
|
|
/* static */
|
|
void ProfilerParentTracker::StartTracking(ProfilerParent* aProfilerParent) {
|
|
ProfilerParentTracker* tracker = GetInstance();
|
|
if (!tracker) {
|
|
return;
|
|
}
|
|
|
|
if (tracker->mMaybeController.isNothing() && tracker->mEntries != 0) {
|
|
// There is no controller yet, but the profiler has started.
|
|
// Since we're adding a ProfilerParent, it's a good time to start
|
|
// controlling the global memory usage of the profiler.
|
|
// (And this helps delay the Controller startup, because the parent profiler
|
|
// can start *very* early in the process, when some resources like threads
|
|
// are not ready yet.)
|
|
tracker->mMaybeController.emplace(size_t(tracker->mEntries) *
|
|
scBytesPerEntry);
|
|
}
|
|
|
|
tracker->mProfilerParents.AppendElement(aProfilerParent);
|
|
}
|
|
|
|
/* static */
|
|
void ProfilerParentTracker::StopTracking(ProfilerParent* aParent) {
|
|
ProfilerParentTracker* tracker = GetInstance();
|
|
if (!tracker) {
|
|
return;
|
|
}
|
|
|
|
tracker->mProfilerParents.RemoveElement(aParent);
|
|
}
|
|
|
|
/* static */
|
|
void ProfilerParentTracker::ProfilerStarted(uint32_t aEntries) {
|
|
ProfilerParentTracker* tracker = GetInstance();
|
|
if (!tracker) {
|
|
return;
|
|
}
|
|
|
|
tracker->mEntries = ClampToAllowedEntries(aEntries);
|
|
|
|
if (tracker->mMaybeController.isNothing() &&
|
|
!tracker->mProfilerParents.IsEmpty()) {
|
|
// We are already tracking child processes, so it's a good time to start
|
|
// controlling the global memory usage of the profiler.
|
|
tracker->mMaybeController.emplace(size_t(tracker->mEntries) *
|
|
scBytesPerEntry);
|
|
}
|
|
}
|
|
|
|
/* static */
|
|
void ProfilerParentTracker::ProfilerWillStopIfStarted() {
|
|
ProfilerParentTracker* tracker = GetInstance();
|
|
if (!tracker) {
|
|
return;
|
|
}
|
|
|
|
tracker->mEntries = 0;
|
|
tracker->mMaybeController = Nothing{};
|
|
}
|
|
|
|
/* static */
|
|
size_t ProfilerParentTracker::ProfilerParentCount() {
|
|
size_t count = 0;
|
|
ProfilerParentTracker* tracker = GetInstance();
|
|
if (tracker) {
|
|
for (ProfilerParent* profilerParent : tracker->mProfilerParents) {
|
|
if (!profilerParent->mDestroyed) {
|
|
++count;
|
|
}
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
template <typename FuncType>
|
|
/* static */
|
|
void ProfilerParentTracker::Enumerate(FuncType&& aIterFunc) {
|
|
ProfilerParentTracker* tracker = GetInstance();
|
|
if (!tracker) {
|
|
return;
|
|
}
|
|
|
|
for (ProfilerParent* profilerParent : tracker->mProfilerParents) {
|
|
if (!profilerParent->mDestroyed) {
|
|
aIterFunc(profilerParent);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename FuncType>
|
|
/* static */
|
|
void ProfilerParentTracker::ForChild(base::ProcessId aChildPid,
|
|
FuncType&& aIterFunc) {
|
|
ProfilerParentTracker* tracker = GetInstance();
|
|
if (!tracker) {
|
|
return;
|
|
}
|
|
|
|
for (ProfilerParent* profilerParent : tracker->mProfilerParents) {
|
|
if (profilerParent->mChildPid == aChildPid) {
|
|
if (!profilerParent->mDestroyed) {
|
|
std::forward<FuncType>(aIterFunc)(profilerParent);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* static */
|
|
void ProfilerParentTracker::ForwardChildChunkManagerUpdate(
|
|
base::ProcessId aProcessId,
|
|
ProfileBufferControlledChunkManager::Update&& aUpdate) {
|
|
ProfilerParentTracker* tracker = GetInstance();
|
|
if (!tracker || tracker->mMaybeController.isNothing()) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(!aUpdate.IsNotUpdate(),
|
|
"No process should ever send a non-update");
|
|
tracker->mMaybeController->HandleChildChunkManagerUpdate(aProcessId,
|
|
std::move(aUpdate));
|
|
}
|
|
|
|
ProfilerParentTracker::ProfilerParentTracker() {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
MOZ_COUNT_CTOR(ProfilerParentTracker);
|
|
}
|
|
|
|
ProfilerParentTracker::~ProfilerParentTracker() {
|
|
// This destructor should only be called on the main thread.
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread() ||
|
|
// OR we're not on the main thread (including if we are
|
|
// past the end of `main()`), which is fine *if* there are
|
|
// no ProfilerParent's still registered, in which case
|
|
// nothing else will happen in this destructor anyway.
|
|
// See bug 1713971 for more information.
|
|
mProfilerParents.IsEmpty());
|
|
MOZ_COUNT_DTOR(ProfilerParentTracker);
|
|
|
|
// Close the channels of any profiler parents that haven't been destroyed.
|
|
for (ProfilerParent* profilerParent : mProfilerParents.Clone()) {
|
|
if (!profilerParent->mDestroyed) {
|
|
// Keep the object alive until the call to Close() has completed.
|
|
// Close() will trigger a call to DeallocPProfilerParent.
|
|
RefPtr<ProfilerParent> actor = profilerParent;
|
|
actor->Close();
|
|
}
|
|
}
|
|
}
|
|
|
|
ProfilerParent::ProfilerParent(base::ProcessId aChildPid)
|
|
: mChildPid(aChildPid), mDestroyed(false) {
|
|
MOZ_COUNT_CTOR(ProfilerParent);
|
|
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
}
|
|
|
|
void ProfilerParent::Init() {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
ProfilerParentTracker::StartTracking(this);
|
|
|
|
// We propagated the profiler state from the parent process to the child
|
|
// process through MOZ_PROFILER_STARTUP* environment variables.
|
|
// However, the profiler state might have changed in this process since then,
|
|
// and now that an active communication channel has been established with the
|
|
// child process, it's a good time to sync up the two profilers again.
|
|
|
|
int entries = 0;
|
|
Maybe<double> duration = Nothing();
|
|
double interval = 0;
|
|
mozilla::Vector<const char*> filters;
|
|
uint32_t features;
|
|
uint64_t activeTabID;
|
|
profiler_get_start_params(&entries, &duration, &interval, &features, &filters,
|
|
&activeTabID);
|
|
|
|
if (entries != 0) {
|
|
ProfilerInitParams ipcParams;
|
|
ipcParams.enabled() = true;
|
|
ipcParams.entries() = entries;
|
|
ipcParams.duration() = duration;
|
|
ipcParams.interval() = interval;
|
|
ipcParams.features() = features;
|
|
ipcParams.activeTabID() = activeTabID;
|
|
|
|
// If the filters exclude our pid, make sure it's stopped, otherwise
|
|
// continue with starting it.
|
|
if (!profiler::detail::FiltersExcludePid(
|
|
filters, ProfilerProcessId::FromNumber(mChildPid))) {
|
|
ipcParams.filters().SetCapacity(filters.length());
|
|
for (const char* filter : filters) {
|
|
ipcParams.filters().AppendElement(filter);
|
|
}
|
|
|
|
Unused << SendEnsureStarted(ipcParams);
|
|
RequestChunkManagerUpdate();
|
|
return;
|
|
}
|
|
}
|
|
|
|
Unused << SendStop();
|
|
}
|
|
#endif // MOZ_GECKO_PROFILER
|
|
|
|
ProfilerParent::~ProfilerParent() {
|
|
MOZ_COUNT_DTOR(ProfilerParent);
|
|
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
#ifdef MOZ_GECKO_PROFILER
|
|
ProfilerParentTracker::StopTracking(this);
|
|
#endif
|
|
}
|
|
|
|
#ifdef MOZ_GECKO_PROFILER
|
|
/* static */
|
|
nsTArray<ProfilerParent::SingleProcessProfilePromiseAndChildPid>
|
|
ProfilerParent::GatherProfiles() {
|
|
nsTArray<SingleProcessProfilePromiseAndChildPid> results;
|
|
if (!NS_IsMainThread()) {
|
|
return results;
|
|
}
|
|
|
|
results.SetCapacity(ProfilerParentTracker::ProfilerParentCount());
|
|
ProfilerParentTracker::Enumerate([&](ProfilerParent* profilerParent) {
|
|
results.AppendElement(SingleProcessProfilePromiseAndChildPid{
|
|
profilerParent->SendGatherProfile(), profilerParent->mChildPid});
|
|
});
|
|
return results;
|
|
}
|
|
|
|
/* static */
|
|
RefPtr<ProfilerParent::SingleProcessProgressPromise>
|
|
ProfilerParent::RequestGatherProfileProgress(base::ProcessId aChildPid) {
|
|
RefPtr<SingleProcessProgressPromise> promise;
|
|
ProfilerParentTracker::ForChild(
|
|
aChildPid, [&promise](ProfilerParent* profilerParent) {
|
|
promise = profilerParent->SendGetGatherProfileProgress();
|
|
});
|
|
return promise;
|
|
}
|
|
|
|
// Magic value for ProfileBufferChunkManagerUpdate::unreleasedBytes meaning
|
|
// that this is a final update from a child.
|
|
constexpr static uint64_t scUpdateUnreleasedBytesFINAL = uint64_t(-1);
|
|
|
|
/* static */
|
|
ProfileBufferChunkManagerUpdate ProfilerParent::MakeFinalUpdate() {
|
|
return ProfileBufferChunkManagerUpdate{
|
|
uint64_t(scUpdateUnreleasedBytesFINAL), 0, TimeStamp{},
|
|
nsTArray<ProfileBufferChunkMetadata>{}};
|
|
}
|
|
|
|
/* static */
|
|
bool ProfilerParent::IsLockedOnCurrentThread() {
|
|
return ProfileBufferGlobalController::IsLockedOnCurrentThread();
|
|
}
|
|
|
|
void ProfilerParent::RequestChunkManagerUpdate() {
|
|
if (mDestroyed) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<AwaitNextChunkManagerUpdatePromise> updatePromise =
|
|
SendAwaitNextChunkManagerUpdate();
|
|
updatePromise->Then(
|
|
GetMainThreadSerialEventTarget(), __func__,
|
|
[self = RefPtr<ProfilerParent>(this)](
|
|
const ProfileBufferChunkManagerUpdate& aUpdate) {
|
|
if (aUpdate.unreleasedBytes() == scUpdateUnreleasedBytesFINAL) {
|
|
// Special value meaning it's the final update from that child.
|
|
ProfilerParentTracker::ForwardChildChunkManagerUpdate(
|
|
self->mChildPid,
|
|
ProfileBufferControlledChunkManager::Update(nullptr));
|
|
} else {
|
|
// Not the final update, translate it.
|
|
std::vector<ProfileBufferControlledChunkManager::ChunkMetadata>
|
|
chunks;
|
|
if (!aUpdate.newlyReleasedChunks().IsEmpty()) {
|
|
chunks.reserve(aUpdate.newlyReleasedChunks().Length());
|
|
for (const ProfileBufferChunkMetadata& chunk :
|
|
aUpdate.newlyReleasedChunks()) {
|
|
chunks.emplace_back(chunk.doneTimeStamp(), chunk.bufferBytes());
|
|
}
|
|
}
|
|
// Let the tracker handle it.
|
|
ProfilerParentTracker::ForwardChildChunkManagerUpdate(
|
|
self->mChildPid,
|
|
ProfileBufferControlledChunkManager::Update(
|
|
aUpdate.unreleasedBytes(), aUpdate.releasedBytes(),
|
|
aUpdate.oldestDoneTimeStamp(), std::move(chunks)));
|
|
// This was not a final update, so start a new request.
|
|
self->RequestChunkManagerUpdate();
|
|
}
|
|
},
|
|
[self = RefPtr<ProfilerParent>(this)](
|
|
mozilla::ipc::ResponseRejectReason aReason) {
|
|
// Rejection could be for a number of reasons, assume the child will
|
|
// not respond anymore, so we pretend we received a final update.
|
|
ProfilerParentTracker::ForwardChildChunkManagerUpdate(
|
|
self->mChildPid,
|
|
ProfileBufferControlledChunkManager::Update(nullptr));
|
|
});
|
|
}
|
|
|
|
// Ref-counted class that resolves a promise on destruction.
|
|
// Usage:
|
|
// RefPtr<GenericPromise> f() {
|
|
// return PromiseResolverOnDestruction::RunTask(
|
|
// [](RefPtr<PromiseResolverOnDestruction> aPromiseResolver){
|
|
// // Give *copies* of aPromiseResolver to asynchronous sub-tasks, the
|
|
// // last remaining RefPtr destruction will resolve the promise.
|
|
// });
|
|
// }
|
|
class PromiseResolverOnDestruction {
|
|
public:
|
|
NS_INLINE_DECL_REFCOUNTING(PromiseResolverOnDestruction)
|
|
|
|
template <typename TaskFunction>
|
|
static RefPtr<GenericPromise> RunTask(TaskFunction&& aTaskFunction) {
|
|
RefPtr<PromiseResolverOnDestruction> promiseResolver =
|
|
new PromiseResolverOnDestruction();
|
|
RefPtr<GenericPromise> promise =
|
|
promiseResolver->mPromiseHolder.Ensure(__func__);
|
|
std::forward<TaskFunction>(aTaskFunction)(std::move(promiseResolver));
|
|
return promise;
|
|
}
|
|
|
|
private:
|
|
PromiseResolverOnDestruction() = default;
|
|
|
|
~PromiseResolverOnDestruction() {
|
|
mPromiseHolder.ResolveIfExists(/* unused */ true, __func__);
|
|
}
|
|
|
|
MozPromiseHolder<GenericPromise> mPromiseHolder;
|
|
};
|
|
|
|
// Given a ProfilerParentSendFunction: (ProfilerParent*) -> some MozPromise,
|
|
// run the function on all live ProfilerParents and return a GenericPromise, and
|
|
// when their promise gets resolve, resolve our Generic promise.
|
|
template <typename ProfilerParentSendFunction>
|
|
static RefPtr<GenericPromise> SendAndConvertPromise(
|
|
ProfilerParentSendFunction&& aProfilerParentSendFunction) {
|
|
if (!NS_IsMainThread()) {
|
|
return GenericPromise::CreateAndResolve(/* unused */ true, __func__);
|
|
}
|
|
|
|
return PromiseResolverOnDestruction::RunTask(
|
|
[&](RefPtr<PromiseResolverOnDestruction> aPromiseResolver) {
|
|
ProfilerParentTracker::Enumerate([&](ProfilerParent* profilerParent) {
|
|
std::forward<ProfilerParentSendFunction>(aProfilerParentSendFunction)(
|
|
profilerParent)
|
|
->Then(GetMainThreadSerialEventTarget(), __func__,
|
|
[aPromiseResolver](
|
|
typename std::remove_reference_t<
|
|
decltype(*std::forward<ProfilerParentSendFunction>(
|
|
aProfilerParentSendFunction)(
|
|
profilerParent))>::ResolveOrRejectValue&&) {
|
|
// Whatever the resolution/rejection is, do nothing.
|
|
// The lambda aPromiseResolver ref-count will decrease.
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
/* static */
|
|
RefPtr<GenericPromise> ProfilerParent::ProfilerStarted(
|
|
nsIProfilerStartParams* aParams) {
|
|
if (!NS_IsMainThread()) {
|
|
return GenericPromise::CreateAndResolve(/* unused */ true, __func__);
|
|
}
|
|
|
|
ProfilerInitParams ipcParams;
|
|
double duration;
|
|
ipcParams.enabled() = true;
|
|
aParams->GetEntries(&ipcParams.entries());
|
|
aParams->GetDuration(&duration);
|
|
if (duration > 0.0) {
|
|
ipcParams.duration() = Some(duration);
|
|
} else {
|
|
ipcParams.duration() = Nothing();
|
|
}
|
|
aParams->GetInterval(&ipcParams.interval());
|
|
aParams->GetFeatures(&ipcParams.features());
|
|
ipcParams.filters() = aParams->GetFilters().Clone();
|
|
// We need filters as a Span<const char*> to test pids in the lambda below.
|
|
auto filtersCStrings = nsTArray<const char*>{aParams->GetFilters().Length()};
|
|
for (const auto& filter : aParams->GetFilters()) {
|
|
filtersCStrings.AppendElement(filter.Data());
|
|
}
|
|
aParams->GetActiveTabID(&ipcParams.activeTabID());
|
|
|
|
ProfilerParentTracker::ProfilerStarted(ipcParams.entries());
|
|
|
|
return SendAndConvertPromise([&](ProfilerParent* profilerParent) {
|
|
if (profiler::detail::FiltersExcludePid(
|
|
filtersCStrings,
|
|
ProfilerProcessId::FromNumber(profilerParent->mChildPid))) {
|
|
// This pid is excluded, don't start the profiler at all.
|
|
return PProfilerParent::StartPromise::CreateAndResolve(/* unused */ true,
|
|
__func__);
|
|
}
|
|
auto promise = profilerParent->SendStart(ipcParams);
|
|
profilerParent->RequestChunkManagerUpdate();
|
|
return promise;
|
|
});
|
|
}
|
|
|
|
/* static */
|
|
void ProfilerParent::ProfilerWillStopIfStarted() {
|
|
if (!NS_IsMainThread()) {
|
|
return;
|
|
}
|
|
|
|
ProfilerParentTracker::ProfilerWillStopIfStarted();
|
|
}
|
|
|
|
/* static */
|
|
RefPtr<GenericPromise> ProfilerParent::ProfilerStopped() {
|
|
return SendAndConvertPromise([](ProfilerParent* profilerParent) {
|
|
return profilerParent->SendStop();
|
|
});
|
|
}
|
|
|
|
/* static */
|
|
RefPtr<GenericPromise> ProfilerParent::ProfilerPaused() {
|
|
return SendAndConvertPromise([](ProfilerParent* profilerParent) {
|
|
return profilerParent->SendPause();
|
|
});
|
|
}
|
|
|
|
/* static */
|
|
RefPtr<GenericPromise> ProfilerParent::ProfilerResumed() {
|
|
return SendAndConvertPromise([](ProfilerParent* profilerParent) {
|
|
return profilerParent->SendResume();
|
|
});
|
|
}
|
|
|
|
/* static */
|
|
RefPtr<GenericPromise> ProfilerParent::ProfilerPausedSampling() {
|
|
return SendAndConvertPromise([](ProfilerParent* profilerParent) {
|
|
return profilerParent->SendPauseSampling();
|
|
});
|
|
}
|
|
|
|
/* static */
|
|
RefPtr<GenericPromise> ProfilerParent::ProfilerResumedSampling() {
|
|
return SendAndConvertPromise([](ProfilerParent* profilerParent) {
|
|
return profilerParent->SendResumeSampling();
|
|
});
|
|
}
|
|
|
|
/* static */
|
|
void ProfilerParent::ClearAllPages() {
|
|
if (!NS_IsMainThread()) {
|
|
return;
|
|
}
|
|
|
|
ProfilerParentTracker::Enumerate([](ProfilerParent* profilerParent) {
|
|
Unused << profilerParent->SendClearAllPages();
|
|
});
|
|
}
|
|
|
|
/* static */
|
|
RefPtr<GenericPromise> ProfilerParent::WaitOnePeriodicSampling() {
|
|
return SendAndConvertPromise([](ProfilerParent* profilerParent) {
|
|
return profilerParent->SendWaitOnePeriodicSampling();
|
|
});
|
|
}
|
|
|
|
void ProfilerParent::ActorDestroy(ActorDestroyReason aActorDestroyReason) {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
mDestroyed = true;
|
|
}
|
|
|
|
#endif
|
|
|
|
} // namespace mozilla
|