Bug 1443443 - Extend PContent to retrieve Performance Counters in the parent process - r=baku
authorTarek Ziadé <tarek@mozilla.com>
Tue, 20 Mar 2018 20:07:41 +0100
changeset 409086 e5d375c99fa4b5c9c978f1ed1d83e30ee2b59998
parent 409085 4959ee444d484f49165b1bd0ed9556a3a7ad2cee
child 409087 a7c5889ae81fdf9adbfbf276fb6d9ef22bbbe755
push id33675
push usertoros@mozilla.com
push dateWed, 21 Mar 2018 09:40:24 +0000
treeherdermozilla-central@f4ddf30ecf57 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1443443
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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',
@@ -100,16 +101,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',
@@ -308,16 +310,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
@@ -1367,16 +1372,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 "mozilla/Sprintf.h"
 
 #ifdef MOZ_WEBRTC
@@ -3324,16 +3325,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
@@ -395,16 +395,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);
 
     /**
@@ -1128,16 +1129,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;