diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /toolkit/components/perfmonitoring/PerformanceMetricsCollector.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/perfmonitoring/PerformanceMetricsCollector.cpp')
-rw-r--r-- | toolkit/components/perfmonitoring/PerformanceMetricsCollector.cpp | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/toolkit/components/perfmonitoring/PerformanceMetricsCollector.cpp b/toolkit/components/perfmonitoring/PerformanceMetricsCollector.cpp new file mode 100644 index 0000000000..9a02c39bc5 --- /dev/null +++ b/toolkit/components/perfmonitoring/PerformanceMetricsCollector.cpp @@ -0,0 +1,315 @@ +/* -*- 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 "nsThreadUtils.h" +#include "mozilla/AbstractThread.h" +#include "mozilla/Logging.h" +#include "mozilla/PerformanceUtils.h" +#include "mozilla/PerformanceMetricsCollector.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/WorkerDebugger.h" +#include "mozilla/dom/WorkerDebuggerManager.h" + +using namespace mozilla; +using namespace mozilla::dom; + +static mozilla::LazyLogModule sPerfLog("PerformanceMetricsCollector"); +#ifdef LOG +# undef LOG +#endif +#define LOG(args) MOZ_LOG(sPerfLog, mozilla::LogLevel::Debug, args) + +namespace mozilla { + +// +// class IPCTimeout +// +NS_IMPL_ISUPPORTS(IPCTimeout, nsITimerCallback, nsINamed) + +// static +IPCTimeout* IPCTimeout::CreateInstance(AggregatedResults* aResults) { + MOZ_ASSERT(aResults); + uint32_t delay = StaticPrefs::dom_performance_children_results_ipc_timeout(); + if (delay == 0) { + return nullptr; + } + return new IPCTimeout(aResults, delay); +} + +IPCTimeout::IPCTimeout(AggregatedResults* aResults, uint32_t aDelay) + : mResults(aResults) { + MOZ_ASSERT(aResults); + MOZ_ASSERT(aDelay > 0); + mozilla::DebugOnly<nsresult> rv = NS_NewTimerWithCallback( + getter_AddRefs(mTimer), this, aDelay, nsITimer::TYPE_ONE_SHOT); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + LOG(("IPCTimeout timer created")); +} + +IPCTimeout::~IPCTimeout() { Cancel(); } + +void IPCTimeout::Cancel() { + if (mTimer) { + LOG(("IPCTimeout timer canceled")); + mTimer->Cancel(); + mTimer = nullptr; + } +} + +NS_IMETHODIMP +IPCTimeout::Notify(nsITimer* aTimer) { + LOG(("IPCTimeout timer triggered")); + mResults->ResolveNow(); + return NS_OK; +} + +NS_IMETHODIMP +IPCTimeout::GetName(nsACString& aName) { + aName.AssignLiteral("IPCTimeout"); + return NS_OK; +} + +// +// class AggregatedResults +// +AggregatedResults::AggregatedResults(nsID aUUID, + PerformanceMetricsCollector* aCollector) + : mPendingResults(0), mCollector(aCollector), mUUID(aUUID) { + MOZ_ASSERT(aCollector); + mIPCTimeout = IPCTimeout::CreateInstance(this); +} + +void AggregatedResults::Abort(nsresult aReason) { + MOZ_ASSERT(!mHolder.IsEmpty()); + MOZ_ASSERT(NS_FAILED(aReason)); + if (mIPCTimeout) { + mIPCTimeout->Cancel(); + mIPCTimeout = nullptr; + } + mHolder.Reject(aReason, __func__); + mPendingResults = 0; +} + +void AggregatedResults::ResolveNow() { + MOZ_ASSERT(!mHolder.IsEmpty()); + LOG(("[%s] Early resolve", nsIDToCString(mUUID).get())); + mHolder.Resolve(CopyableTArray(mData), __func__); + mIPCTimeout = nullptr; + mCollector->ForgetAggregatedResults(mUUID); +} + +void AggregatedResults::AppendResult( + const nsTArray<dom::PerformanceInfo>& aMetrics) { + if (mHolder.IsEmpty()) { + // A previous call failed and the promise was already rejected + return; + } + MOZ_ASSERT(mPendingResults > 0); + + // Each PerformanceInfo is converted into a PerformanceInfoDictionary + for (const PerformanceInfo& result : aMetrics) { + mozilla::dom::Sequence<mozilla::dom::CategoryDispatchDictionary> items; + + for (const CategoryDispatch& entry : result.items()) { + uint32_t count = entry.count(); + if (count == 0) { + continue; + } + CategoryDispatchDictionary* item = items.AppendElement(fallible); + if (NS_WARN_IF(!item)) { + Abort(NS_ERROR_OUT_OF_MEMORY); + return; + } + item->mCategory = entry.category(); + item->mCount = count; + } + + PerformanceInfoDictionary* data = mData.AppendElement(fallible); + if (NS_WARN_IF(!data)) { + Abort(NS_ERROR_OUT_OF_MEMORY); + return; + } + data->mPid = result.pid(); + data->mWindowId = result.windowId(); + data->mHost.Assign(result.host()); + data->mDuration = result.duration(); + data->mCounterId = result.counterId(); + data->mIsWorker = result.isWorker(); + data->mIsTopLevel = result.isTopLevel(); + data->mMemoryInfo.mDomDom = result.memory().domDom(); + data->mMemoryInfo.mDomStyle = result.memory().domStyle(); + data->mMemoryInfo.mDomOther = result.memory().domOther(); + data->mMemoryInfo.mJsMemUsage = result.memory().jsMemUsage(); + data->mMemoryInfo.mMedia.mAudioSize = result.memory().media().audioSize(); + data->mMemoryInfo.mMedia.mVideoSize = result.memory().media().videoSize(); + data->mMemoryInfo.mMedia.mResourcesSize = + result.memory().media().resourcesSize(); + data->mItems = items; + } + + mPendingResults--; + if (mPendingResults) { + return; + } + + LOG(("[%s] All data collected, resolving promise", + nsIDToCString(mUUID).get())); + if (mIPCTimeout) { + mIPCTimeout->Cancel(); + mIPCTimeout = nullptr; + } + nsTArray<dom::PerformanceInfoDictionary> data; + data.Assign(mData); + mHolder.Resolve(std::move(data), __func__); + mCollector->ForgetAggregatedResults(mUUID); +} + +void AggregatedResults::SetNumResultsRequired(uint32_t aNumResultsRequired) { + MOZ_ASSERT(!mPendingResults && aNumResultsRequired); + mPendingResults = aNumResultsRequired; +} + +RefPtr<RequestMetricsPromise> AggregatedResults::GetPromise() { + return mHolder.Ensure(__func__); +} + +// +// class PerformanceMetricsCollector (singleton) +// + +// raw pointer for the singleton +PerformanceMetricsCollector* gInstance = nullptr; + +PerformanceMetricsCollector::~PerformanceMetricsCollector() { + MOZ_ASSERT(gInstance == this); + gInstance = nullptr; +} + +void PerformanceMetricsCollector::ForgetAggregatedResults(const nsID& aUUID) { + MOZ_ASSERT(gInstance); + MOZ_ASSERT(XRE_IsParentProcess()); + // This Remove() call will trigger AggregatedResults DTOR and if its + // the last in the table, the DTOR of PerformanceMetricsCollector. + // That's why we need to make sure we hold a reference here before the call + RefPtr<PerformanceMetricsCollector> kungFuDeathGrip = this; + LOG(("[%s] Removing from the table", nsIDToCString(aUUID).get())); + mAggregatedResults.Remove(aUUID); +} + +// static +RefPtr<RequestMetricsPromise> PerformanceMetricsCollector::RequestMetrics() { + MOZ_ASSERT(XRE_IsParentProcess()); + RefPtr<PerformanceMetricsCollector> pmc = gInstance; + if (!pmc) { + pmc = new PerformanceMetricsCollector(); + gInstance = pmc; + } + return pmc->RequestMetricsInternal(); +} + +RefPtr<RequestMetricsPromise> +PerformanceMetricsCollector::RequestMetricsInternal() { + // each request has its own UUID + nsID uuid; + nsresult rv = nsID::GenerateUUIDInPlace(uuid); + if (NS_WARN_IF(NS_FAILED(rv))) { + return RequestMetricsPromise::CreateAndReject(rv, __func__); + } + + LOG(("[%s] Requesting Performance Metrics", nsIDToCString(uuid).get())); + + // Getting all content processes + nsTArray<ContentParent*> children; + ContentParent::GetAll(children); + uint32_t numChildren = children.Length(); + + // keep track of all results in an AggregatedResults instance + UniquePtr<AggregatedResults> results = + MakeUnique<AggregatedResults>(uuid, this); + RefPtr<RequestMetricsPromise> promise = results->GetPromise(); + + // We want to get back as many results as children + one parent if needed + uint32_t numResultsRequired = children.Length(); + nsTArray<RefPtr<PerformanceInfoPromise>> localPromises = + CollectPerformanceInfo(); + if (!localPromises.IsEmpty()) { + numResultsRequired++; + } + + LOG(("[%s] Expecting %d results back", nsIDToCString(uuid).get(), + numResultsRequired)); + results->SetNumResultsRequired(numResultsRequired); + const auto& aggregatedResult = + mAggregatedResults.InsertOrUpdate(uuid, std::move(results)); + + // calling all content processes via IPDL (async) + for (uint32_t i = 0; i < numChildren; i++) { + if (NS_WARN_IF(!children[i]->SendRequestPerformanceMetrics(uuid))) { + LOG(("[%s] Failed to send request to child %d", nsIDToCString(uuid).get(), + i)); + aggregatedResult->Abort(NS_ERROR_FAILURE); + return RequestMetricsPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + LOG(("[%s] Request sent to child %d", nsIDToCString(uuid).get(), i)); + } + + nsTArray<RefPtr<PerformanceInfoPromise>> promises = CollectPerformanceInfo(); + if (promises.IsEmpty()) { + return promise; + } + + // collecting the current process PerformanceInfo + PerformanceInfoPromise::All(NS_GetCurrentThread(), localPromises) + ->Then( + NS_GetCurrentThread(), __func__, + [uuid](const nsTArray<mozilla::dom::PerformanceInfo> aResult) { + LOG(("[%s] Local CollectPerformanceInfo promise resolved", + nsIDToCString(uuid).get())); + DataReceived(uuid, aResult); + }, + [](const nsresult aResult) {}); + + return promise; +} + +// static +nsresult PerformanceMetricsCollector::DataReceived( + const nsID& aUUID, const nsTArray<PerformanceInfo>& aMetrics) { + // If some content process were unresponsive on shutdown, we may get called + // here with late data received from children - so instead of asserting + // that gInstance is available, we just return. + if (!gInstance) { + LOG(("[%s] gInstance is gone", nsIDToCString(aUUID).get())); + return NS_OK; + } + MOZ_ASSERT(XRE_IsParentProcess()); + return gInstance->DataReceivedInternal(aUUID, aMetrics); +} + +nsresult PerformanceMetricsCollector::DataReceivedInternal( + const nsID& aUUID, const nsTArray<PerformanceInfo>& aMetrics) { + MOZ_ASSERT(gInstance == this); + auto results = mAggregatedResults.Lookup(aUUID); + if (!results) { + LOG(("[%s] UUID is gone from mAggregatedResults", + nsIDToCString(aUUID).get())); + return NS_ERROR_FAILURE; + } + + LOG(("[%s] Received one PerformanceInfo array", nsIDToCString(aUUID).get())); + AggregatedResults* aggregatedResults = results->get(); + MOZ_ASSERT(aggregatedResults); + + // If this is the last result, AppendResult() will trigger the deletion + // of this collector, nothing should be done after this line. + aggregatedResults->AppendResult(aMetrics); + return NS_OK; +} + +} // namespace mozilla |