Bug 1443443 - Extend PContent to retrieve Performance Counters in the parent process - r?baku draft
authorTarek Ziadé <tarek@mozilla.com>
Tue, 20 Mar 2018 20:07:41 +0100
changeset 770120 78e3194e55e03fa38c971b40b4b4aebd0b6ee9b4
parent 767726 0d81c80876dd09536f78d1158e1e6ff78f9ad226
push id103335
push usertziade@mozilla.com
push dateTue, 20 Mar 2018 19:50:54 +0000
reviewersbaku
bugs1443443
milestone61.0a1
Bug 1443443 - Extend PContent to retrieve Performance Counters in the parent process - r?baku Adds the IPDL layer to asynchronously retrieve in the parent process the performance counters. MozReview-Commit-ID: RbKstNx8pi
dom/base/ChromeUtils.cpp
dom/base/ChromeUtils.h
dom/base/DocGroup.cpp
dom/base/DocGroup.h
dom/base/moz.build
dom/base/nsIPerformanceMetrics.idl
dom/base/nsPerformanceMetrics.cpp
dom/base/nsPerformanceMetrics.h
dom/chrome-webidl/ChromeUtils.webidl
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/DOMTypes.ipdlh
dom/ipc/PContent.ipdl
dom/tests/browser/browser.ini
dom/tests/browser/browser_test_performance_metrics.js
dom/workers/WorkerDebugger.cpp
dom/workers/WorkerDebugger.h
dom/workers/WorkerDebuggerManager.cpp
dom/workers/WorkerDebuggerManager.h
--- a/dom/base/ChromeUtils.cpp
+++ b/dom/base/ChromeUtils.cpp
@@ -8,16 +8,17 @@
 
 #include "jsfriendapi.h"
 #include "WrapperFactory.h"
 
 #include "mozilla/Base64.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/CycleCollectedJSRuntime.h"
 #include "mozilla/TimeStamp.h"
+#include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/IdleDeadline.h"
 #include "mozilla/dom/UnionTypes.h"
 #include "mozilla/dom/WindowBinding.h" // For IdleRequestCallback/Options
 #include "nsThreadUtils.h"
 #include "mozJSComponentLoader.h"
 #include "GeckoProfiler.h"
 
 namespace mozilla {
@@ -648,16 +649,29 @@ ChromeUtils::ClearRecentJSDevError(Globa
 {
   auto runtime = CycleCollectedJSRuntime::Get();
   MOZ_ASSERT(runtime);
 
   runtime->ClearRecentDevError();
 }
 #endif // NIGHTLY_BUILD
 
+#ifndef RELEASE_OR_BETA
+/* static */ void
+ChromeUtils::RequestPerformanceMetrics(GlobalObject&)
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+  nsTArray<ContentParent*> children;
+  ContentParent::GetAll(children);
+  for (uint32_t i = 0; i < children.Length(); i++) {
+    mozilla::Unused << children[i]->SendRequestPerformanceMetrics();
+  }
+}
+#endif
+
 constexpr auto kSkipSelfHosted = JS::SavedFrameSelfHosted::Exclude;
 
 /* static */ void
 ChromeUtils::GetCallerLocation(const GlobalObject& aGlobal, nsIPrincipal* aPrincipal,
                                JS::MutableHandle<JSObject*> aRetval)
 {
   JSContext* cx = aGlobal.Context();
 
--- a/dom/base/ChromeUtils.h
+++ b/dom/base/ChromeUtils.h
@@ -150,16 +150,20 @@ public:
                            ErrorResult& aRv);
 
   static void GetRecentJSDevError(GlobalObject& aGlobal,
                                   JS::MutableHandleValue aRetval,
                                   ErrorResult& aRv);
 
   static void ClearRecentJSDevError(GlobalObject& aGlobal);
 
+#ifndef RELEASE_OR_BETA
+  static void RequestPerformanceMetrics(GlobalObject& aGlobal);
+#endif
+
   static void Import(const GlobalObject& aGlobal,
                      const nsAString& aResourceURI,
                      const Optional<JS::Handle<JSObject*>>& aTargetObj,
                      JS::MutableHandle<JSObject*> aRetval,
                      ErrorResult& aRv);
 
   static void DefineModuleGetter(const GlobalObject& global,
                                  JS::Handle<JSObject*> target,
--- a/dom/base/DocGroup.cpp
+++ b/dom/base/DocGroup.cpp
@@ -1,19 +1,25 @@
 /* -*- 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/dom/DocGroup.h"
+#include "mozilla/dom/DOMTypes.h"
 #include "mozilla/dom/TabGroup.h"
 #include "mozilla/Telemetry.h"
 #include "nsIDocShell.h"
 #include "nsDOMMutationObserver.h"
+#if defined(XP_WIN)
+#include <processthreadsapi.h>  // for GetCurrentProcessId()
+#else
+#include <unistd.h> // for getpid()
+#endif // defined(XP_WIN)
 
 namespace mozilla {
 namespace dom {
 
 AutoTArray<RefPtr<DocGroup>, 2>* DocGroup::sPendingDocGroups = nullptr;
 
 /* static */ nsresult
 DocGroup::GetKey(nsIPrincipal* aPrincipal, nsACString& aKey)
@@ -56,16 +62,78 @@ DocGroup::~DocGroup()
   if (!NS_IsMainThread()) {
     nsIEventTarget* target = EventTargetFor(TaskCategory::Other);
     NS_ProxyRelease("DocGroup::mReactionsStack", target, mReactionsStack.forget());
   }
 
   mTabGroup->mDocGroups.RemoveEntry(mKey);
 }
 
+#ifndef RELEASE_OR_BETA
+PerformanceInfo
+DocGroup::ReportPerformanceInfo()
+{
+  AssertIsOnMainThread();
+#if defined(XP_WIN)
+  uint32_t pid = GetCurrentProcessId();
+#else
+  uint32_t pid = getpid();
+#endif
+  uint64_t wid = 0;
+  uint64_t pwid = 0;
+  uint16_t count = 0;
+  uint64_t duration = 0;
+  nsCString host = NS_LITERAL_CSTRING("None");
+
+  for (const auto& document : *this) {
+    // grabbing the host name of the first document
+    nsCOMPtr<nsIDocument> doc = do_QueryInterface(document);
+    MOZ_ASSERT(doc);
+    nsCOMPtr<nsIURI> docURI = doc->GetDocumentURI();
+    if (!docURI) {
+      continue;
+    }
+    docURI->GetHost(host);
+    wid = doc->OuterWindowID();
+
+    // getting the top window id - if not possible
+    // pwid gets the same value than wid
+    pwid = wid;
+    nsPIDOMWindowInner* win = doc->GetInnerWindow();
+    if (win) {
+      nsPIDOMWindowOuter* outer = win->GetOuterWindow();
+      if (outer) {
+        nsCOMPtr<nsPIDOMWindowOuter> top = outer->GetTop();
+        if (top) {
+          pwid = top->WindowID();
+        }
+      }
+    }
+  }
+
+  duration = mPerformanceCounter->GetExecutionDuration();
+  FallibleTArray<CategoryDispatch> items;
+
+  // now that we have the host and window ids, let's look at the perf counters
+  for (uint32_t index = 0; index < (uint32_t)TaskCategory::Count; index++) {
+    TaskCategory category = static_cast<TaskCategory>(index);
+    count = mPerformanceCounter->GetDispatchCount(DispatchCategory(category));
+    CategoryDispatch item = CategoryDispatch(index, count);
+    if (!items.AppendElement(item, fallible)) {
+      NS_ERROR("Could not complete the operation");
+      return PerformanceInfo(host, pid, wid, pwid, duration, false, items);
+    }
+  }
+
+  // setting back all counters to zero
+  mPerformanceCounter->ResetPerformanceCounters();
+  return PerformanceInfo(host, pid, wid, pwid, duration, false, items);
+}
+#endif
+
 nsresult
 DocGroup::Dispatch(TaskCategory aCategory,
                    already_AddRefed<nsIRunnable>&& aRunnable)
 {
 #ifndef RELEASE_OR_BETA
   mPerformanceCounter->IncrementDispatchCounter(DispatchCategory(aCategory));
 #endif
   return mTabGroup->DispatchWithDocGroup(aCategory, Move(aRunnable), this);
--- a/dom/base/DocGroup.h
+++ b/dom/base/DocGroup.h
@@ -18,16 +18,18 @@
 #include "mozilla/dom/HTMLSlotElement.h"
 #include "mozilla/PerformanceCounter.h"
 
 
 namespace mozilla {
 class AbstractThread;
 namespace dom {
 
+class PerformanceInfo;
+
 // Two browsing contexts are considered "related" if they are reachable from one
 // another through window.opener, window.parent, or window.frames. This is the
 // spec concept of a "unit of related browsing contexts"
 //
 // Two browsing contexts are considered "similar-origin" if they can be made to
 // have the same origin by setting document.domain. This is the spec concept of
 // a "unit of similar-origin related browsing contexts"
 //
@@ -56,16 +58,19 @@ public:
   {
     return aKey == mKey;
   }
 #ifndef RELEASE_OR_BETA
   PerformanceCounter* GetPerformanceCounter()
   {
     return mPerformanceCounter;
   }
+
+  PerformanceInfo
+  ReportPerformanceInfo();
 #endif
   TabGroup* GetTabGroup()
   {
     return mTabGroup;
   }
   mozilla::dom::CustomElementReactionsStack* CustomElementReactionsStack()
   {
     MOZ_ASSERT(NS_IsMainThread());
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -21,16 +21,17 @@ XPIDL_SOURCES += [
     'nsIDOMDOMRequest.idl',
     'nsIDOMParser.idl',
     'nsIDOMSerializer.idl',
     'nsIDroppedLinkHandler.idl',
     'nsIFrameLoader.idl',
     'nsIImageLoadingContent.idl',
     'nsIMessageManager.idl',
     'nsIObjectLoadingContent.idl',
+    'nsIPerformanceMetrics.idl',
     'nsIRemoteWindowContext.idl',
     'nsIScriptChannel.idl',
     'nsISelection.idl',
     'nsISelectionController.idl',
     'nsISelectionDisplay.idl',
     'nsISelectionListener.idl',
     'nsISelectionPrivate.idl',
     'nsISlowScriptDebug.idl',
@@ -104,16 +105,17 @@ EXPORTS += [
     'nsITimeoutHandler.h',
     'nsJSEnvironment.h',
     'nsJSUtils.h',
     'nsLineBreaker.h',
     'nsMappedAttributeElement.h',
     'nsNameSpaceManager.h',
     'nsNodeInfoManager.h',
     'nsNodeUtils.h',
+    'nsPerformanceMetrics.h',
     'nsPIDOMWindow.h',
     'nsPIDOMWindowInlines.h',
     'nsPIWindowRoot.h',
     'nsPropertyTable.h',
     'nsRange.h',
     'nsSandboxFlags.h',
     'nsStructuredCloneContainer.h',
     'nsStubAnimationObserver.h',
@@ -313,16 +315,17 @@ UNIFIED_SOURCES += [
     'nsMappedAttributeElement.cpp',
     'nsMappedAttributes.cpp',
     'nsMimeTypeArray.cpp',
     'nsNameSpaceManager.cpp',
     'nsNoDataProtocolContentPolicy.cpp',
     'nsNodeInfoManager.cpp',
     'nsNodeUtils.cpp',
     'nsOpenURIInFrameParams.cpp',
+    'nsPerformanceMetrics.cpp',
     'nsPlainTextSerializer.cpp',
     'nsPropertyTable.cpp',
     'nsQueryContentEventResult.cpp',
     'nsRange.cpp',
     'nsScreen.cpp',
     'nsScriptNameSpaceManager.cpp',
     'nsStructuredCloneContainer.cpp',
     'nsStubAnimationObserver.cpp',
new file mode 100644
--- /dev/null
+++ b/dom/base/nsIPerformanceMetrics.idl
@@ -0,0 +1,49 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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 "nsISupports.idl"
+#include "nsIArray.idl"
+
+/*
+ * nsIPerformanceMetricsData is used to store performance data collected
+ * in all content processes by nsThread and nsWorkerThread.
+ *
+ * Each (host, category, pid, wid, pwid) is unique to a given DocGroup or
+ * Worker, and we collect the number of dispatches and execution duration.
+ *
+ * This XPCOM interface reflects the data collected in Performance counters.
+ * see xpcom/threads/PerformanceCounter.h
+ */
+[scriptable, builtinclass, uuid(1f9a58c9-be37-4463-8996-c7f5b9a5bef8)]
+interface nsIPerformanceMetricsDispatchCategory : nsISupports
+{
+  // DispatchCategory value
+  readonly attribute unsigned long category;
+  // Number of dispatch.
+  readonly attribute unsigned long count;
+};
+
+
+[scriptable, builtinclass, uuid(02b0cdc6-4be2-4154-a8a9-e8d462073200)]
+interface nsIPerformanceMetricsData : nsISupports
+{
+  // Host of the document, if any
+  readonly attribute AUTF8String host;
+  // process id
+  readonly attribute unsigned long pid;
+  // window id
+  readonly attribute unsigned long long wid;
+  // "parent" window id
+  readonly attribute unsigned long long pwid;
+  // Execution time in microseconds
+  readonly attribute unsigned long long duration;
+  // True if the data is collected in a worker
+  readonly attribute bool worker;
+  // Dispatch Category counters
+  readonly attribute nsIArray items;
+};
+
+
new file mode 100644
--- /dev/null
+++ b/dom/base/nsPerformanceMetrics.cpp
@@ -0,0 +1,124 @@
+/* -*- 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/. */
+
+#include <nsIMutableArray.h>
+#include <nsArrayUtils.h>
+#include <nsPerformanceMetrics.h>
+
+/* ------------------------------------------------------
+ *
+ * class PerformanceMetricsDispatchCategory
+ *
+ */
+
+PerformanceMetricsDispatchCategory::PerformanceMetricsDispatchCategory(uint32_t aCategory, uint32_t aCount)
+  : mCategory(aCategory), mCount(aCount)
+{
+}
+
+NS_IMPL_ISUPPORTS(PerformanceMetricsDispatchCategory,
+                  nsIPerformanceMetricsDispatchCategory);
+
+
+NS_IMETHODIMP
+PerformanceMetricsDispatchCategory::GetCategory(uint32_t* aCategory)
+{
+  *aCategory = mCategory;
+  return NS_OK;
+};
+
+NS_IMETHODIMP
+PerformanceMetricsDispatchCategory::GetCount(uint32_t* aCount)
+{
+  *aCount = mCount;
+  return NS_OK;
+};
+
+/* ------------------------------------------------------
+ *
+ * class PerformanceMetricsData
+ *
+ */
+
+PerformanceMetricsData::PerformanceMetricsData(uint32_t aPid, uint64_t aWid,
+                                               uint64_t aPwid, const nsCString& aHost,
+                                               uint64_t aDuration, bool aWorker,
+                                               nsIArray* aItems)
+  : mPid(aPid), mWid(aWid), mPwid(aPwid), mHost(aHost)
+  , mDuration(aDuration), mWorker(aWorker)
+{
+    uint32_t len;
+    nsresult rv = aItems->GetLength(&len);
+    if (NS_FAILED(rv)) {
+      NS_ASSERTION(rv == NS_OK, "Failed to ge the length");
+    }
+    for (uint32_t i = 0; i < len; i++) {
+        nsCOMPtr<nsIPerformanceMetricsDispatchCategory> item = do_QueryElementAt(aItems, i);
+        mItems.AppendElement(item);
+    }
+};
+
+NS_IMPL_ISUPPORTS(PerformanceMetricsData, nsIPerformanceMetricsData);
+
+NS_IMETHODIMP
+PerformanceMetricsData::GetHost(nsACString& aHost)
+{
+  aHost = mHost;
+  return NS_OK;
+};
+
+NS_IMETHODIMP
+PerformanceMetricsData::GetWorker(bool* aWorker)
+{
+  *aWorker = mWorker;
+  return NS_OK;
+};
+
+NS_IMETHODIMP
+PerformanceMetricsData::GetPid(uint32_t* aPid)
+{
+  *aPid = mPid;
+  return NS_OK;
+};
+
+NS_IMETHODIMP
+PerformanceMetricsData::GetWid(uint64_t* aWid)
+{
+  *aWid = mWid;
+  return NS_OK;
+};
+
+NS_IMETHODIMP
+PerformanceMetricsData::GetDuration(uint64_t* aDuration)
+{
+  *aDuration = mDuration;
+  return NS_OK;
+};
+
+NS_IMETHODIMP
+PerformanceMetricsData::GetPwid(uint64_t* aPwid)
+{
+  *aPwid = mPwid;
+  return NS_OK;
+};
+
+NS_IMETHODIMP
+PerformanceMetricsData::GetItems(nsIArray** aItems)
+{
+  NS_ENSURE_ARG_POINTER(aItems);
+  *aItems = nullptr;
+
+  nsresult rv = NS_OK;
+  nsCOMPtr<nsIMutableArray> items =
+    do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  uint32_t len = mItems.Length();
+  for (uint32_t i = 0; i < len; i++) {
+    items->AppendElement(mItems[i]);
+  }
+
+  items.forget(aItems);
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/base/nsPerformanceMetrics.h
@@ -0,0 +1,46 @@
+/* -*- 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/. */
+
+#ifndef nsPerformanceMetrics_h___
+#define nsPerformanceMetrics_h___
+
+#include "nsIPerformanceMetrics.h"
+
+
+class PerformanceMetricsDispatchCategory final : public nsIPerformanceMetricsDispatchCategory
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIPERFORMANCEMETRICSDISPATCHCATEGORY
+  PerformanceMetricsDispatchCategory(uint32_t aCategory, uint32_t aCount);
+private:
+  ~PerformanceMetricsDispatchCategory() = default;
+
+  uint32_t mCategory;
+  uint32_t mCount;
+};
+
+
+class PerformanceMetricsData final : public nsIPerformanceMetricsData
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIPERFORMANCEMETRICSDATA
+  PerformanceMetricsData(uint32_t aPid, uint64_t aWid, uint64_t aPwid, const nsCString& aHost,
+                         uint64_t aDuration, bool aWorker, nsIArray* aItems);
+private:
+  ~PerformanceMetricsData() = default;
+
+  uint32_t mPid;
+  uint64_t mWid;
+  uint64_t mPwid;
+  nsCString mHost;
+  uint64_t mDuration;
+  bool mWorker;
+  nsCOMArray<nsIPerformanceMetricsDispatchCategory> mItems;
+};
+
+#endif  // end nsPerformanceMetrics_h__
--- a/dom/chrome-webidl/ChromeUtils.webidl
+++ b/dom/chrome-webidl/ChromeUtils.webidl
@@ -118,16 +118,20 @@ namespace ChromeUtils {
   readonly attribute any recentJSDevError;
 
   /**
    * Reset `recentJSDevError` to `undefined` for the current JSRuntime.
    */
   void clearRecentJSDevError();
 #endif // NIGHTLY_BUILD
 
+#ifndef RELEASE_OR_BETA
+  void requestPerformanceMetrics();
+#endif
+
   /**
    * IF YOU ADD NEW METHODS HERE, MAKE SURE THEY ARE THREAD-SAFE.
    */
 };
 
 /**
  * Additional ChromeUtils methods that are _not_ thread-safe, and hence not
  * exposed in workers.
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -38,16 +38,18 @@
 #include "mozilla/dom/MemoryReportRequest.h"
 #include "mozilla/dom/PLoginReputationChild.h"
 #include "mozilla/dom/ProcessGlobal.h"
 #include "mozilla/dom/PushNotifier.h"
 #include "mozilla/dom/ServiceWorkerManager.h"
 #include "mozilla/dom/TabGroup.h"
 #include "mozilla/dom/nsIContentChild.h"
 #include "mozilla/dom/URLClassifierChild.h"
+#include "mozilla/dom/WorkerDebugger.h"
+#include "mozilla/dom/WorkerDebuggerManager.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/gfx/Logging.h"
 #include "mozilla/psm/PSMContentListener.h"
 #include "mozilla/hal_sandbox/PHalChild.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/FileDescriptorSetChild.h"
 #include "mozilla/ipc/FileDescriptorUtils.h"
 #include "mozilla/ipc/GeckoChildProcessHost.h"
@@ -73,16 +75,19 @@
 #include "nsBaseDragService.h"
 #include "mozilla/media/MediaChild.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/WebBrowserPersistDocumentChild.h"
 #include "mozilla/HangDetails.h"
 #include "imgLoader.h"
 #include "GMPServiceChild.h"
 #include "NullPrincipal.h"
+#include "nsIPerformanceMetrics.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIWorkerDebuggerManager.h"
 
 #if !defined(XP_WIN)
 #include "mozilla/Omnijar.h"
 #endif
 
 #ifdef MOZ_GECKO_PROFILER
 #include "ChildProfilerController.h"
 #endif
@@ -1369,16 +1374,48 @@ ContentChild::GetResultForRenderingInitF
 
   // If we are talking to the GPU process, then we should recover from this on
   // the next ContentChild::RecvReinitRendering call.
   gfxCriticalNote << "Could not initialize rendering with GPU process";
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
+ContentChild::RecvRequestPerformanceMetrics()
+{
+#ifndef RELEASE_OR_BETA
+  // iterate on all WorkerDebugger
+  RefPtr<WorkerDebuggerManager> wdm = WorkerDebuggerManager::GetOrCreate();
+  if (NS_WARN_IF(!wdm)) {
+    return IPC_OK();
+  }
+
+  for (uint32_t index = 0; index < wdm->GetDebuggersLength(); index++) {
+    WorkerDebugger* debugger = wdm->GetDebuggerAt(index);
+    MOZ_ASSERT(debugger);
+    SendAddPerformanceMetrics(debugger->ReportPerformanceInfo());
+  }
+
+  // iterate on all DocGroup
+  nsTArray<RefPtr<TabChild>> tabs = TabChild::GetAll();
+  for (const auto& tabChild : tabs) {
+    TabGroup* tabGroup = tabChild->TabGroup();
+    for (auto iter = tabGroup->Iter(); !iter.Done(); iter.Next()) {
+        RefPtr<DocGroup> docGroup = iter.Get()->mDocGroup;
+        SendAddPerformanceMetrics(docGroup->ReportPerformanceInfo());
+    }
+  }
+  return IPC_OK();
+#endif
+#ifdef RELEASE_OR_BETA
+  return IPC_OK();
+#endif
+}
+
+mozilla::ipc::IPCResult
 ContentChild::RecvInitRendering(Endpoint<PCompositorManagerChild>&& aCompositor,
                                 Endpoint<PImageBridgeChild>&& aImageBridge,
                                 Endpoint<PVRManagerChild>&& aVRBridge,
                                 Endpoint<PVideoDecoderManagerChild>&& aVideoManager,
                                 nsTArray<uint32_t>&& namespaces)
 {
   MOZ_ASSERT(namespaces.Length() == 3);
 
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -185,16 +185,19 @@ public:
   RecvInitRendering(
     Endpoint<PCompositorManagerChild>&& aCompositor,
     Endpoint<PImageBridgeChild>&& aImageBridge,
     Endpoint<PVRManagerChild>&& aVRBridge,
     Endpoint<PVideoDecoderManagerChild>&& aVideoManager,
     nsTArray<uint32_t>&& namespaces) override;
 
   mozilla::ipc::IPCResult
+  RecvRequestPerformanceMetrics() override;
+
+  mozilla::ipc::IPCResult
   RecvReinitRendering(
     Endpoint<PCompositorManagerChild>&& aCompositor,
     Endpoint<PImageBridgeChild>&& aImageBridge,
     Endpoint<PVRManagerChild>&& aVRBridge,
     Endpoint<PVideoDecoderManagerChild>&& aVideoManager,
     nsTArray<uint32_t>&& namespaces) override;
 
   virtual mozilla::ipc::IPCResult RecvAudioDefaultDeviceChange() override;
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -187,16 +187,17 @@
 #include "nsPluginHost.h"
 #include "nsPluginTags.h"
 #include "nsIBlocklistService.h"
 #include "mozilla/StyleSheet.h"
 #include "mozilla/StyleSheetInlines.h"
 #include "nsHostObjectProtocolHandler.h"
 #include "nsICaptivePortalService.h"
 #include "nsIObjectLoadingContent.h"
+#include "nsPerformanceMetrics.h"
 
 #include "nsIBidiKeyboard.h"
 
 #include "nsLayoutStylesheetCache.h"
 
 #include "ContentPrefs.h"
 #include "mozilla/Sprintf.h"
 
@@ -3268,16 +3269,54 @@ ContentParent::RecvFinishMemoryReport(co
 {
   if (mMemoryReportRequest) {
     mMemoryReportRequest->Finish(aGeneration);
     mMemoryReportRequest = nullptr;
   }
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+ContentParent::RecvAddPerformanceMetrics(const PerformanceInfo& aMetrics)
+{
+#ifndef RELEASE_OR_BETA
+  // converting the data we get from a child as a notification
+  if (aMetrics.items().IsEmpty()) {
+      return IPC_OK();
+  }
+
+  nsCOMPtr<nsIMutableArray> xpItems = do_CreateInstance(NS_ARRAY_CONTRACTID);
+  if (NS_WARN_IF(!xpItems)) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  for (uint32_t i = 0; i<aMetrics.items().Length(); i++) {
+       const CategoryDispatch& entry = aMetrics.items()[i];
+       nsCOMPtr<nsIPerformanceMetricsDispatchCategory> item =
+           new PerformanceMetricsDispatchCategory(entry.category(),
+                                                  entry.count());
+       xpItems->AppendElement(item);
+  }
+
+  nsCOMPtr<nsIPerformanceMetricsData> data =
+      new PerformanceMetricsData(aMetrics.pid(), aMetrics.wid(), aMetrics.pwid(),
+                                 aMetrics.host(), aMetrics.duration(),
+                                 aMetrics.worker(), xpItems);
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (!obs) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+  obs->NotifyObservers(data, "performance-metrics", nullptr);
+  return IPC_OK();
+#endif
+#ifdef RELEASE_OR_BETA
+  return IPC_OK();
+#endif
+}
+
 PCycleCollectWithLogsParent*
 ContentParent::AllocPCycleCollectWithLogsParent(const bool& aDumpAllTraces,
                                                 const FileDescriptor& aGCLog,
                                                 const FileDescriptor& aCCLog)
 {
   MOZ_CRASH("Don't call this; use ContentParent::CycleCollectWithLogs");
 }
 
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -843,16 +843,17 @@ private:
    * Get or create the corresponding content parent array to |aContentProcessType|.
    */
   static nsTArray<ContentParent*>& GetOrCreatePool(const nsAString& aContentProcessType);
 
   virtual mozilla::ipc::IPCResult RecvInitBackground(Endpoint<mozilla::ipc::PBackgroundParent>&& aEndpoint) override;
 
   mozilla::ipc::IPCResult RecvAddMemoryReport(const MemoryReport& aReport) override;
   mozilla::ipc::IPCResult RecvFinishMemoryReport(const uint32_t& aGeneration) override;
+  mozilla::ipc::IPCResult RecvAddPerformanceMetrics(const PerformanceInfo& aMetrics) override;
 
   virtual bool
   DeallocPJavaScriptParent(mozilla::jsipc::PJavaScriptParent*) override;
 
   virtual bool
   DeallocPRemoteSpellcheckEngineParent(PRemoteSpellcheckEngineParent*) override;
 
   virtual PBrowserParent* AllocPBrowserParent(const TabId& aTabId,
--- a/dom/ipc/DOMTypes.ipdlh
+++ b/dom/ipc/DOMTypes.ipdlh
@@ -119,10 +119,48 @@ struct CreatedWindowInfo
   nsCString urlToLoad;
   TextureFactoryIdentifier textureFactoryIdentifier;
   uint64_t layersId;
   CompositorOptions compositorOptions;
   uint32_t maxTouchPoints;
   DimensionInfo dimensions;
 };
 
+
+/**
+ * PerformanceInfo is used to pass performance info stored
+ * in WorkerPrivate & DocGroup instances
+ *
+ * Each (host, pid, wid, pwid) is unique to a given DocGroup or
+ * Worker, and we collect the number of dispatches per Dispatch
+ * category and total execution duration.
+ *
+ * This IPDL struct reflects the data collected in Performance counters.
+ * see xpcom/threads/PerformanceCounter.h
+ */
+struct CategoryDispatch
+{
+  // DispatchCategory value
+  uint16_t category;
+  // Number of dispatch
+  uint16_t count;
+};
+
+struct PerformanceInfo
+{
+  // Host of the document, if any
+  nsCString host;
+  // process id
+  uint16_t pid;
+  // window id
+  uint64_t wid;
+  // "parent" window id
+  uint64_t pwid;
+  // Execution time in microseconds
+  uint64_t duration;
+  // True if the data is collected in a worker
+  bool worker;
+  // Counters per category. For workers, a single entry
+  CategoryDispatch[] items;
+};
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -390,16 +390,17 @@ child:
      * abnormally exit if this fails; the details are OS-specific.
      */
     async SetProcessSandbox(MaybeFileDesc aBroker);
 
     async RequestMemoryReport(uint32_t generation,
                               bool anonymize,
                               bool minimizeMemoryUsage,
                               MaybeFileDesc DMDFile);
+    async RequestPerformanceMetrics();
 
     /**
      * Communication between the PuppetBidiKeyboard and the actual
      * BidiKeyboard hosted by the parent
      */
     async BidiKeyboardNotify(bool isLangRTL, bool haveBidiKeyboards);
 
     /**
@@ -1122,16 +1123,18 @@ parent:
                              IHandlerControlHolder aHandlerControl);
 
     async AddMemoryReport(MemoryReport aReport);
     async FinishMemoryReport(uint32_t aGeneration);
 
     async MaybeReloadPlugins();
 
     async BHRThreadHang(HangDetails aHangDetails);
+
+    async AddPerformanceMetrics(PerformanceInfo aMetrics);
 both:
      async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
                         Principal aPrincipal, ClonedMessageData aData);
 
     /**
      * Notify `push-subscription-modified` observers in the parent and child.
      */
     async NotifyPushSubscriptionModifiedObservers(nsCString scope,
--- a/dom/tests/browser/browser.ini
+++ b/dom/tests/browser/browser.ini
@@ -67,8 +67,9 @@ support-files =
 support-files =
   test_new_window_from_content_child.html
 [browser_xhr_sandbox.js]
 [browser_noopener.js]
 support-files =
   test_noopener_source.html
   test_noopener_target.html
 [browser_noopener_null_uri.js]
+[browser_test_performance_metrics.js]
new file mode 100644
--- /dev/null
+++ b/dom/tests/browser/browser_test_performance_metrics.js
@@ -0,0 +1,55 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=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/. */
+
+const TEST_URL = "http://example.com/browser/dom/tests/browser/dummy.html";
+ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+
+
+add_task(async function test() {
+  if (!AppConstants.RELEASE_OR_BETA) {
+    SpecialPowers.setBoolPref('dom.performance.enable_scheduler_timing', true);
+    waitForExplicitFinish();
+
+    await BrowserTestUtils.withNewTab({ gBrowser, url: "http://example.com" },
+      async function(browser) {
+
+      // grab events..
+      var events = [];
+      function getInfoFromService(subject, topic, value) {
+        subject = subject.QueryInterface(Ci.nsIPerformanceMetricsData);
+        if (subject.host == "example.com") {
+          events.push(subject);
+        }
+      }
+      Services.obs.addObserver(getInfoFromService, "performance-metrics");
+
+      // trigger an IPDL call
+      ChromeUtils.requestPerformanceMetrics();
+
+      // wait until we get the events back
+      await BrowserTestUtils.waitForCondition(() => {
+        return events.length > 0;
+      }, "wait for events to come in", 100, 20);
+
+      // let's check the last example.com tab event we got
+      let last = events[0];
+      Assert.equal(last.host, "example.com", "host should be example.com");
+      Assert.ok(last.duration > 0, "Duration should be positive");
+
+      // let's look at the XPCOM data we got back
+      let items = last.items.QueryInterface(Ci.nsIMutableArray);
+      let enumerator = items.enumerate();
+      let total = 0;
+      while (enumerator.hasMoreElements()) {
+        let item = enumerator.getNext();
+        item = item.QueryInterface(Ci.nsIPerformanceMetricsDispatchCategory);
+        total += item.count;
+      }
+      Assert.ok(total > 0);
+    });
+    SpecialPowers.clearUserPref('dom.performance.enable_scheduler_timing');
+  }
+});
--- a/dom/workers/WorkerDebugger.cpp
+++ b/dom/workers/WorkerDebugger.cpp
@@ -12,16 +12,21 @@
 #include "nsQueryObject.h"
 #include "nsThreadUtils.h"
 #include "ScriptLoader.h"
 #include "WorkerCommon.h"
 #include "WorkerError.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 #include "WorkerScope.h"
+#if defined(XP_WIN)
+#include <processthreadsapi.h>  // for GetCurrentProcessId()
+#else
+#include <unistd.h> // for getpid()
+#endif // defined(XP_WIN)
 
 namespace mozilla {
 namespace dom {
 
 namespace {
 
 class DebuggerMessageEventRunnable : public WorkerDebuggerRunnable
 {
@@ -466,11 +471,49 @@ WorkerDebugger::ReportErrorToDebuggerOnM
   }
 
   WorkerErrorReport report;
   report.mMessage = aMessage;
   report.mFilename = aFilename;
   WorkerErrorReport::LogErrorToConsole(report, 0);
 }
 
+#ifndef RELEASE_OR_BETA
+PerformanceInfo
+WorkerDebugger::ReportPerformanceInfo()
+{
+  AssertIsOnMainThread();
+#if defined(XP_WIN)
+  uint32_t pid = GetCurrentProcessId();
+#else
+  uint32_t pid = getpid();
+#endif
+  uint64_t wid = mWorkerPrivate->WindowID();
+  uint64_t pwid = wid;
+  nsPIDOMWindowInner* win = mWorkerPrivate->GetWindow();
+  if (win) {
+    nsPIDOMWindowOuter* outer = win->GetOuterWindow();
+    if (outer) {
+      nsCOMPtr<nsPIDOMWindowOuter> top = outer->GetTop();
+      if (top) {
+        pwid = top->WindowID();
+      }
+    }
+  }
+  RefPtr<PerformanceCounter> perf = mWorkerPrivate->GetPerformanceCounter();
+  uint16_t count =  perf->GetTotalDispatchCount();
+  uint64_t duration = perf->GetExecutionDuration();
+  RefPtr<nsIURI> uri = mWorkerPrivate->GetResolvedScriptURI();
+  CategoryDispatch item = CategoryDispatch(DispatchCategory::Worker.GetValue(), count);
+  FallibleTArray<CategoryDispatch> items;
+  if (!items.AppendElement(item, fallible)) {
+    NS_ERROR("Could not complete the operation");
+    return PerformanceInfo(uri->GetSpecOrDefault(), pid, wid, pwid, duration,
+                           true, items);
+  }
+  perf->ResetPerformanceCounters();
+  return PerformanceInfo(uri->GetSpecOrDefault(), pid, wid, pwid, duration,
+                         true, items);
+}
+#endif
 
 } // dom namespace
 } // mozilla namespace
--- a/dom/workers/WorkerDebugger.h
+++ b/dom/workers/WorkerDebugger.h
@@ -2,16 +2,17 @@
 /* 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/. */
 
 #ifndef mozilla_dom_workers_WorkerDebugger_h
 #define mozilla_dom_workers_WorkerDebugger_h
 
+#include "mozilla/dom/DOMTypes.h"
 #include "mozilla/dom/WorkerCommon.h"
 #include "nsIWorkerDebugger.h"
 
 namespace mozilla {
 namespace dom {
 
 class WorkerPrivate;
 
@@ -38,16 +39,25 @@ public:
 
   void
   PostMessageToDebugger(const nsAString& aMessage);
 
   void
   ReportErrorToDebugger(const nsAString& aFilename, uint32_t aLineno,
                         const nsAString& aMessage);
 
+#ifndef RELEASE_OR_BETA
+  /*
+   * Sends back a PerformanceInfo struct from the counters
+   * in mWorkerPrivate. Counters are reset to zero after this call.
+   */
+  PerformanceInfo
+  ReportPerformanceInfo();
+#endif
+
 private:
   virtual
   ~WorkerDebugger();
 
   void
   PostMessageToDebuggerOnMainThread(const nsAString& aMessage);
 
   void
--- a/dom/workers/WorkerDebuggerManager.cpp
+++ b/dom/workers/WorkerDebuggerManager.cpp
@@ -356,10 +356,22 @@ WorkerDebuggerManager::UnregisterDebugge
   for (size_t index = 0; index < listeners.Length(); ++index) {
     listeners[index]->OnUnregister(debugger);
   }
 
   debugger->Close();
   aWorkerPrivate->SetIsDebuggerRegistered(false);
 }
 
+uint32_t
+WorkerDebuggerManager::GetDebuggersLength() const
+{
+  return mDebuggers.Length();
+}
+
+WorkerDebugger*
+WorkerDebuggerManager::GetDebuggerAt(uint32_t aIndex) const
+{
+  return mDebuggers.SafeElementAt(aIndex, nullptr);
+}
+
 } // dom namespace
 } // mozilla namespace
--- a/dom/workers/WorkerDebuggerManager.h
+++ b/dom/workers/WorkerDebuggerManager.h
@@ -69,16 +69,22 @@ public:
                              bool aNotifyListeners);
 
   void
   UnregisterDebugger(WorkerPrivate* aWorkerPrivate);
 
   void
   UnregisterDebuggerMainThread(WorkerPrivate* aWorkerPrivate);
 
+  uint32_t
+  GetDebuggersLength() const;
+
+  WorkerDebugger*
+  GetDebuggerAt(uint32_t aIndex) const;
+
 private:
   virtual ~WorkerDebuggerManager();
 };
 
 inline nsresult
 RegisterWorkerDebugger(WorkerPrivate* aWorkerPrivate)
 {
   WorkerDebuggerManager* manager;