summaryrefslogtreecommitdiffstats
path: root/toolkit/components/perfmonitoring
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /toolkit/components/perfmonitoring
parentInitial commit. (diff)
downloadfirefox-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')
-rw-r--r--toolkit/components/perfmonitoring/PerformanceMetricsCollector.cpp315
-rw-r--r--toolkit/components/perfmonitoring/PerformanceMetricsCollector.h106
-rw-r--r--toolkit/components/perfmonitoring/PerformanceTypes.h31
-rw-r--r--toolkit/components/perfmonitoring/PerformanceUtils.cpp186
-rw-r--r--toolkit/components/perfmonitoring/PerformanceUtils.h32
-rw-r--r--toolkit/components/perfmonitoring/moz.build23
6 files changed, 693 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
diff --git a/toolkit/components/perfmonitoring/PerformanceMetricsCollector.h b/toolkit/components/perfmonitoring/PerformanceMetricsCollector.h
new file mode 100644
index 0000000000..1b9e90f3b4
--- /dev/null
+++ b/toolkit/components/perfmonitoring/PerformanceMetricsCollector.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef PerformanceMetricsCollector_h
+#define PerformanceMetricsCollector_h
+
+#include "nsIObserver.h"
+#include "nsITimer.h"
+#include "nsID.h"
+#include "nsTHashMap.h"
+#include "mozilla/dom/ChromeUtilsBinding.h" // defines PerformanceInfoDictionary
+#include "mozilla/dom/DOMTypes.h" // defines PerformanceInfo
+#include "mozilla/PerformanceTypes.h"
+
+namespace mozilla {
+
+namespace dom {
+class Promise;
+}
+
+class PerformanceMetricsCollector;
+class AggregatedResults;
+
+class IPCTimeout final : public nsITimerCallback, public nsINamed {
+ public:
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+ NS_DECL_ISUPPORTS
+ static IPCTimeout* CreateInstance(AggregatedResults* aResults);
+ void Cancel();
+
+ private:
+ IPCTimeout(AggregatedResults* aResults, uint32_t aDelay);
+ ~IPCTimeout();
+
+ nsCOMPtr<nsITimer> mTimer;
+ AggregatedResults* mResults;
+};
+
+// AggregatedResults receives PerformanceInfo results that are collected
+// asynchronously via IPDL from all content processes.
+// They are converted into an array of
+// PerformanceInfoDictionary dictionaries (webidl)
+//
+// Once every process have sent back its results, AggregatedResults will
+// resolve the MozPromise returned by GetPromise()
+// with all the collected data.
+//
+// See ChromeUtils::RequestPerformanceMetrics.
+class AggregatedResults final {
+ public:
+ AggregatedResults(nsID aUUID, PerformanceMetricsCollector* aCollector);
+ ~AggregatedResults() = default;
+ void AppendResult(const nsTArray<dom::PerformanceInfo>& aMetrics);
+ void SetNumResultsRequired(uint32_t aNumResultsRequired);
+ void Abort(nsresult aReason);
+ void ResolveNow();
+ RefPtr<RequestMetricsPromise> GetPromise();
+
+ private:
+ RefPtr<IPCTimeout> mIPCTimeout;
+ MozPromiseHolder<RequestMetricsPromise> mHolder;
+ uint32_t mPendingResults;
+ FallibleTArray<dom::PerformanceInfoDictionary> mData;
+
+ // AggregatedResults keeps a reference on the collector
+ // so it gets destructed when all pending AggregatedResults
+ // are themselves destructed when removed from
+ // PerformanceMetricsCollector::mAggregatedResults.
+ //
+ // This lifecycle ensures that everything is released once
+ // all pending results are sent.
+ RefPtr<PerformanceMetricsCollector> mCollector;
+ nsID mUUID;
+};
+
+//
+// PerformanceMetricsCollector is instanciated as a singleton, and creates
+// one AggregatedResults instance everytime metrics are requested.
+//
+// Each AggregatedResults has a unique identifier (UUID) that is used
+// to send metrics requests via IPDL. When metrics are back in an
+// asynchronous fashion, the UUID is used to append the data to the
+// right AggregatedResults instance and eventually let it resolve the
+// linked promise.
+//
+class PerformanceMetricsCollector final {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(PerformanceMetricsCollector)
+ static RefPtr<RequestMetricsPromise> RequestMetrics();
+ static nsresult DataReceived(const nsID& aUUID,
+ const nsTArray<dom::PerformanceInfo>& aMetrics);
+ void ForgetAggregatedResults(const nsID& aUUID);
+
+ private:
+ ~PerformanceMetricsCollector();
+ RefPtr<RequestMetricsPromise> RequestMetricsInternal();
+ nsresult DataReceivedInternal(const nsID& aUUID,
+ const nsTArray<dom::PerformanceInfo>& aMetrics);
+ nsTHashMap<nsID, UniquePtr<AggregatedResults>> mAggregatedResults;
+};
+
+} // namespace mozilla
+#endif // PerformanceMetricsCollector_h
diff --git a/toolkit/components/perfmonitoring/PerformanceTypes.h b/toolkit/components/perfmonitoring/PerformanceTypes.h
new file mode 100644
index 0000000000..9ef53c1d13
--- /dev/null
+++ b/toolkit/components/perfmonitoring/PerformanceTypes.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef PerformanceTypes_h
+#define PerformanceTypes_h
+
+#include "mozilla/MozPromise.h"
+
+namespace mozilla {
+
+namespace dom {
+class PerformanceInfo;
+class PerformanceMemoryInfo;
+struct PerformanceInfoDictionary;
+} // namespace dom
+
+/**
+ * Promises definitions
+ */
+typedef MozPromise<dom::PerformanceInfo, nsresult, true> PerformanceInfoPromise;
+typedef MozPromise<nsTArray<dom::PerformanceInfoDictionary>, nsresult, true>
+ RequestMetricsPromise;
+typedef MozPromise<nsTArray<dom::PerformanceInfo>, nsresult, true>
+ PerformanceInfoArrayPromise;
+typedef MozPromise<mozilla::dom::PerformanceMemoryInfo, nsresult, true>
+ MemoryPromise;
+
+} // namespace mozilla
+#endif // PerformanceTypes_h
diff --git a/toolkit/components/perfmonitoring/PerformanceUtils.cpp b/toolkit/components/perfmonitoring/PerformanceUtils.cpp
new file mode 100644
index 0000000000..08ee695854
--- /dev/null
+++ b/toolkit/components/perfmonitoring/PerformanceUtils.cpp
@@ -0,0 +1,186 @@
+/* -*- 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 "mozilla/PerformanceUtils.h"
+
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/BrowsingContextGroup.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/WorkerDebugger.h"
+#include "mozilla/dom/WorkerDebuggerManager.h"
+
+#include "MediaDecoder.h"
+#include "jsfriendapi.h"
+#include "nsGlobalWindowOuter.h"
+#include "nsWindowSizes.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+nsTArray<RefPtr<PerformanceInfoPromise>> CollectPerformanceInfo() {
+ nsTArray<RefPtr<PerformanceInfoPromise>> promises;
+
+ // collecting ReportPerformanceInfo from all WorkerDebugger instances
+ RefPtr<mozilla::dom::WorkerDebuggerManager> wdm =
+ WorkerDebuggerManager::GetOrCreate();
+ if (NS_WARN_IF(!wdm)) {
+ return promises;
+ }
+
+ for (uint32_t i = 0; i < wdm->GetDebuggersLength(); i++) {
+ const RefPtr<WorkerDebugger> debugger = wdm->GetDebuggerAt(i);
+ promises.AppendElement(debugger->ReportPerformanceInfo());
+ }
+
+ nsTArray<RefPtr<BrowsingContextGroup>> groups;
+ BrowsingContextGroup::GetAllGroups(groups);
+
+ nsTArray<DocGroup*> docGroups;
+ for (auto& browsingContextGroup : groups) {
+ browsingContextGroup->GetDocGroups(docGroups);
+ }
+
+ for (DocGroup* docGroup : docGroups) {
+ promises.AppendElement(docGroup->ReportPerformanceInfo());
+ }
+
+ return promises;
+}
+
+static void AddWindowTabSizes(nsGlobalWindowOuter* aWindow,
+ nsTabSizes* aSizes) {
+ Document* document = aWindow->GetDocument();
+ if (document && document->GetCachedSizes(aSizes)) {
+ // We got a cached version
+ return;
+ }
+ // We measure the sizes on a fresh nsTabSizes instance
+ // because we want to cache the value and aSizes might
+ // already have some values from other windows.
+ nsTabSizes sizes;
+
+ // Measure the window.
+ SizeOfState state(moz_malloc_size_of);
+ nsWindowSizes windowSizes(state);
+ aWindow->AddSizeOfIncludingThis(windowSizes);
+ // Measure the inner window, if there is one.
+ nsGlobalWindowInner* inner = aWindow->GetCurrentInnerWindowInternal();
+ if (inner != nullptr) {
+ inner->AddSizeOfIncludingThis(windowSizes);
+ }
+ windowSizes.addToTabSizes(&sizes);
+ if (document) {
+ document->SetCachedSizes(&sizes);
+ }
+ aSizes->mDom += sizes.mDom;
+ aSizes->mStyle += sizes.mStyle;
+ aSizes->mOther += sizes.mOther;
+}
+
+RefPtr<MemoryPromise> CollectMemoryInfo(
+ const RefPtr<DocGroup>& aDocGroup,
+ const RefPtr<AbstractThread>& aEventTarget) {
+ // Getting Dom sizes.
+ nsTabSizes sizes;
+
+ using WindowSet = mozilla::HashSet<nsGlobalWindowOuter*>;
+ WindowSet windowsVisited;
+ for (const auto& document : *aDocGroup) {
+ nsGlobalWindowOuter* window =
+ document ? nsGlobalWindowOuter::Cast(document->GetWindow()) : nullptr;
+ if (window) {
+ WindowSet::AddPtr p = windowsVisited.lookupForAdd(window);
+ if (!p) {
+ // We have not seen this window before.
+ AddWindowTabSizes(window, &sizes);
+ if (!windowsVisited.add(p, window)) {
+ // OOM. Let us stop counting memory, we may undercount.
+ break;
+ }
+ }
+ }
+ }
+
+ using ZoneSet = mozilla::HashSet<JS::Zone*>;
+ using SharedSet = mozilla::HashSet<void*>;
+ ZoneSet zonesVisited;
+ SharedSet sharedVisited;
+ // Getting JS-related memory usage
+ uint64_t jsMemUsed = 0;
+ nsTArray<RefPtr<MediaMemoryPromise>> mediaMemoryPromises;
+ for (auto* doc : *aDocGroup) {
+ bool unused;
+ nsIGlobalObject* globalObject = doc->GetScriptHandlingObject(unused);
+ if (globalObject) {
+ JSObject* object = globalObject->GetGlobalJSObject();
+ if (object != nullptr) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "We cannot get the object zone on another thread");
+ JS::Zone* zone = JS::GetObjectZone(object);
+ ZoneSet::AddPtr addZone = zonesVisited.lookupForAdd(zone);
+ if (!addZone) {
+ // We have not checked this zone before.
+ jsMemUsed += js::GetMemoryUsageForZone(zone);
+ if (!zonesVisited.add(addZone, zone)) {
+ // OOM. Let us stop counting memory, we may undercount.
+ break;
+ }
+
+ const js::gc::SharedMemoryMap& shared =
+ js::GetSharedMemoryUsageForZone(zone);
+ for (auto iter = shared.iter(); !iter.done(); iter.next()) {
+ void* sharedMem = iter.get().key();
+ SharedSet::AddPtr addShared = sharedVisited.lookupForAdd(sharedMem);
+ if (addShared) {
+ // We *have* seen this shared memory before.
+
+ // Because shared memory is already included in
+ // js::GetMemoryUsageForZone() above, and we've seen it for a
+ // previous zone, we subtract it here so it's not counted more
+ // than once.
+ jsMemUsed -= iter.get().value().nbytes;
+ } else if (!sharedVisited.add(addShared, sharedMem)) {
+ // As above, abort with an under-estimate.
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ mediaMemoryPromises.AppendElement(GetMediaMemorySizes(doc));
+ }
+
+ // Getting Media sizes.
+ return MediaMemoryPromise::All(aEventTarget, mediaMemoryPromises)
+ ->Then(
+ aEventTarget, __func__,
+ [jsMemUsed, sizes](const nsTArray<MediaMemoryInfo> mediaArray) {
+ size_t audioSize = 0;
+ size_t videoSize = 0;
+ size_t resourcesSize = 0;
+
+ for (auto media : mediaArray) {
+ audioSize += media.audioSize();
+ videoSize += media.videoSize();
+ resourcesSize += media.resourcesSize();
+ }
+
+ return MemoryPromise::CreateAndResolve(
+ PerformanceMemoryInfo(
+ MediaMemoryInfo(audioSize, videoSize, resourcesSize),
+ sizes.mDom, sizes.mStyle, sizes.mOther, jsMemUsed),
+ __func__);
+ },
+ [](const nsresult rv) {
+ return MemoryPromise::CreateAndReject(rv, __func__);
+ });
+}
+
+} // namespace mozilla
diff --git a/toolkit/components/perfmonitoring/PerformanceUtils.h b/toolkit/components/perfmonitoring/PerformanceUtils.h
new file mode 100644
index 0000000000..7c01795f28
--- /dev/null
+++ b/toolkit/components/perfmonitoring/PerformanceUtils.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef PerformanceUtils_h
+#define PerformanceUtils_h
+
+#include "mozilla/PerformanceTypes.h"
+
+class nsPIDOMWindowOuter;
+
+namespace mozilla {
+namespace dom {
+class BrowsingContext;
+class DocGroup;
+} // namespace dom
+/**
+ * Returns an array of promises to asynchronously collect all performance
+ * info in the current process.
+ */
+nsTArray<RefPtr<PerformanceInfoPromise>> CollectPerformanceInfo();
+
+/**
+ * Asynchronously collects memory info for a given window
+ */
+RefPtr<MemoryPromise> CollectMemoryInfo(
+ const RefPtr<dom::DocGroup>& aDocGroup,
+ const RefPtr<AbstractThread>& aEventTarget);
+
+} // namespace mozilla
+#endif // PerformanceUtils_h
diff --git a/toolkit/components/perfmonitoring/moz.build b/toolkit/components/perfmonitoring/moz.build
new file mode 100644
index 0000000000..193e5df7db
--- /dev/null
+++ b/toolkit/components/perfmonitoring/moz.build
@@ -0,0 +1,23 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Toolkit", "Performance Monitoring")
+
+
+UNIFIED_SOURCES += ["PerformanceMetricsCollector.cpp", "PerformanceUtils.cpp"]
+
+EXPORTS.mozilla += [
+ "PerformanceMetricsCollector.h",
+ "PerformanceTypes.h",
+ "PerformanceUtils.h",
+]
+
+LOCAL_INCLUDES += ["/dom/base"]
+
+FINAL_LIBRARY = "xul"
+
+include("/ipc/chromium/chromium-config.mozbuild")