Bug 1447768 - part 2 - Dispatch counters in the parent process - r=baku
authorTarek Ziadé <tarek@mozilla.com>
Wed, 04 Apr 2018 13:36:25 +0200
changeset 411704 90eb45ff0a64561f4a60a61e6e5e59eccee07439
parent 411703 71c9c86c3c8d792a83eb0403fc06c8b249302840
child 411705 eb110ef25ecf8d3a1f9a1a6151ddc17175a99eb9
child 411750 72c60d1c546120b9d3ec47a41f9271f44bcb8308
push id101729
push usercsabou@mozilla.com
push dateWed, 04 Apr 2018 18:07:35 +0000
treeherdermozilla-inbound@3c240f56a113 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1447768
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 1447768 - part 2 - Dispatch counters in the parent process - r=baku Chromeutils.RequestPerformanceMetrics() is now composed of two parts: - calls content processes via IPDL to get their counters - directly dispatch counters from the parent process MozReview-Commit-ID: HlgcEOzkyAq
dom/base/ChromeUtils.cpp
dom/ipc/DOMTypes.ipdlh
dom/tests/browser/browser.ini
dom/tests/browser/browser_test_performance_metrics.js
dom/tests/browser/ping_worker.html
dom/tests/browser/ping_worker.js
dom/workers/WorkerDebugger.cpp
dom/workers/WorkerPrivate.cpp
dom/workers/WorkerPrivate.h
xpcom/threads/TaskCategory.h
--- a/dom/base/ChromeUtils.cpp
+++ b/dom/base/ChromeUtils.cpp
@@ -7,16 +7,19 @@
 #include "ChromeUtils.h"
 
 #include "jsfriendapi.h"
 #include "WrapperFactory.h"
 
 #include "mozilla/Base64.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/CycleCollectedJSRuntime.h"
+#ifndef RELEASE_OR_BETA
+#include "mozilla/PerformanceUtils.h"
+#endif
 #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"
@@ -654,21 +657,35 @@ ChromeUtils::ClearRecentJSDevError(Globa
 }
 #endif // NIGHTLY_BUILD
 
 #ifndef RELEASE_OR_BETA
 /* static */ void
 ChromeUtils::RequestPerformanceMetrics(GlobalObject&)
 {
   MOZ_ASSERT(XRE_IsParentProcess());
+
+  // calling all content processes via IPDL (async)
   nsTArray<ContentParent*> children;
   ContentParent::GetAll(children);
   for (uint32_t i = 0; i < children.Length(); i++) {
     mozilla::Unused << children[i]->SendRequestPerformanceMetrics();
   }
+
+
+  // collecting the current process counters and notifying them
+  nsTArray<PerformanceInfo> info;
+  CollectPerformanceInfo(info);
+  SystemGroup::Dispatch(TaskCategory::Performance,
+    NS_NewRunnableFunction(
+      "RequestPerformanceMetrics",
+      [info]() { mozilla::Unused << NS_WARN_IF(NS_FAILED(NotifyPerformanceInfo(info))); }
+    )
+  );
+
 }
 #endif
 
 constexpr auto kSkipSelfHosted = JS::SavedFrameSelfHosted::Exclude;
 
 /* static */ void
 ChromeUtils::GetCallerLocation(const GlobalObject& aGlobal, nsIPrincipal* aPrincipal,
                                JS::MutableHandle<JSObject*> aRetval)
--- a/dom/ipc/DOMTypes.ipdlh
+++ b/dom/ipc/DOMTypes.ipdlh
@@ -145,17 +145,17 @@ struct CategoryDispatch
   uint16_t count;
 };
 
 struct PerformanceInfo
 {
   // Host of the document, if any
   nsCString host;
   // process id
-  uint16_t pid;
+  uint32_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;
--- a/dom/tests/browser/browser.ini
+++ b/dom/tests/browser/browser.ini
@@ -4,16 +4,17 @@ support-files =
   page_privatestorageevent.html
   page_localstorage_e10s.html
   position.html
   test-console-api.html
   test_bug1004814.html
   worker_bug1004814.js
   geo_leak_test.html
   dummy.html
+  ping_worker.html
   test_largeAllocation.html
   test_largeAllocation.html^headers^
   test_largeAllocation2.html
   test_largeAllocation2.html^headers^
   test_largeAllocationFormSubmit.sjs
   helper_largeAllocation.js
   !/dom/tests/mochitest/geolocation/network_geolocation.sjs
 
--- a/dom/tests/browser/browser_test_performance_metrics.js
+++ b/dom/tests/browser/browser_test_performance_metrics.js
@@ -1,55 +1,116 @@
 /* -*- 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");
+const ROOT_URL = "http://example.com/browser/dom/tests/browser";
+const DUMMY_URL = ROOT_URL + "/dummy.html";
+const WORKER_URL = ROOT_URL + "/ping_worker.html";
+
+
+let nextId = 0;
 
+function jsonrpc(tab, method, params) {
+  let currentId = nextId++;
+  let messageManager = tab.linkedBrowser.messageManager;
+  messageManager.sendAsyncMessage("jsonrpc", {
+    id: currentId,
+    method: method,
+    params: params
+  });
+  return new Promise(function (resolve, reject) {
+    messageManager.addMessageListener("jsonrpc", function listener(event) {
+      let { id, result, error } = event.data;
+      if (id !== currentId) {
+        return;
+      }
+      messageManager.removeMessageListener("jsonrpc", listener);
+      if (error) {
+        reject(error);
+        return;
+      }
+      resolve(result);
+    });
+  });
+}
+
+function postMessageToWorker(tab, message) {
+  return jsonrpc(tab, "postMessageToWorker", [WORKER_URL, message]);
+}
 
 add_task(async function test() {
-  if (!AppConstants.RELEASE_OR_BETA) {
-    SpecialPowers.setBoolPref('dom.performance.enable_scheduler_timing', true);
-    waitForExplicitFinish();
+  SpecialPowers.setBoolPref('dom.performance.enable_scheduler_timing', true);
+  waitForExplicitFinish();
 
-    await BrowserTestUtils.withNewTab({ gBrowser, url: "http://example.com" },
-      async function(browser) {
+  // Load 3 pages and wait. The 3rd one has a worker
+  let page1 = await BrowserTestUtils.openNewForegroundTab({
+    gBrowser, opening: 'about:about', forceNewProcess: false
+  });
 
-      // 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");
+  let page2 = await BrowserTestUtils.openNewForegroundTab({
+    gBrowser, opening: 'about:memory', forceNewProcess: false
+  });
+
+  let page3 = await BrowserTestUtils.openNewForegroundTab({
+    gBrowser, opening: "about:performance", forceNewProcess: true
+  });
+
+  let parent_process_event = false;
+  let worker_event = false;
 
-      // 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);
+  // load a 4th tab with a worker
+  await BrowserTestUtils.withNewTab({ gBrowser, url: WORKER_URL },
+    async function(browser) {
+    // grab events..
+    var events = [];
+    function getInfoFromService(subject, topic, value) {
+      subject = subject.QueryInterface(Ci.nsIMutableArray);
+      let enumerator = subject.enumerate();
+      while (enumerator.hasMoreElements()) {
+        let item = enumerator.getNext();
+        item = item.QueryInterface(Ci.nsIPerformanceMetricsData);
+        if (item.pid == Services.appinfo.processID) {
+          parent_process_event = true;
+        }
+        if (item.worker) {
+          worker_event = true;
+        }
+        events.push(item);
+      }
+    }
 
-      // 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");
+    Services.obs.addObserver(getInfoFromService, "performance-metrics");
+
+    // wait until we get some events back by triggering requestPerformanceMetrics
+    await BrowserTestUtils.waitForCondition(() => {
+      ChromeUtils.requestPerformanceMetrics();
+      return events.length > 10;
+    }, "wait for events to come in", 500, 10);
 
+    BrowserTestUtils.removeTab(page1);
+    BrowserTestUtils.removeTab(page2);
+    BrowserTestUtils.removeTab(page3);
+
+    // let's check the events
+    let duration = 0;
+    let total = 0;
+    for (let i=0; i < events.length; i++) {
+      duration += events[i].duration;
       // let's look at the XPCOM data we got back
-      let items = last.items.QueryInterface(Ci.nsIMutableArray);
+      let items = events[i].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');
-  }
+    }
+
+    Assert.ok(duration > 0, "Duration should be positive");
+    Assert.ok(total > 0, "Should get a positive count");
+    Assert.ok(parent_process_event, "parent process sent back some events");
+  });
+
+  SpecialPowers.clearUserPref('dom.performance.enable_scheduler_timing');
 });
new file mode 100644
--- /dev/null
+++ b/dom/tests/browser/ping_worker.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+<head>
+  <script type="text/javascript">
+
+  var myWorker;
+  function init() {
+   myWorker = new Worker('ping_worker.js');
+   myWorker.postMessage("ping");
+  }
+
+  </script>
+</head>
+<body onload="init()">
+  <h1>A page with a worker</h1>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/browser/ping_worker.js
@@ -0,0 +1,11 @@
+/* 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/. */
+
+"use strict";
+
+function messageListener(event) {
+  postMessage("pong");
+}
+
+addEventListener("message", { handleEvent: messageListener });
--- a/dom/workers/WorkerDebugger.cpp
+++ b/dom/workers/WorkerDebugger.cpp
@@ -497,22 +497,26 @@ WorkerDebugger::ReportPerformanceInfo()
         pwid = top->WindowID();
       }
     }
   }
   RefPtr<PerformanceCounter> perf = mWorkerPrivate->GetPerformanceCounter();
   uint16_t count =  perf->GetTotalDispatchCount();
   uint64_t duration = perf->GetExecutionDuration();
   RefPtr<nsIURI> uri = mWorkerPrivate->GetResolvedScriptURI();
+
+  // Workers only produce metrics for a single category - DispatchCategory::Worker.
+  // We still return an array of CategoryDispatch so the PerformanceInfo
+  // struct is common to all performance counters throughout Firefox.
+  FallibleTArray<CategoryDispatch> items;
   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);
+                            true, items);
   }
   perf->ResetPerformanceCounters();
   return PerformanceInfo(uri->GetSpecOrDefault(), pid, wid, pwid, duration,
                          true, items);
 }
 #endif
 
 } // dom namespace
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -419,20 +419,24 @@ private:
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     aWorkerPrivate->AssertIsOnWorkerThread();
 
     if (NS_WARN_IF(!aWorkerPrivate->EnsureClientSource())) {
       return false;
     }
 
-    // PerformanceStorage needs to be initialized on the worker thread before
-    // being used on main-thread. Let's be sure that it is created before any
+    // PerformanceStorage & PerformanceCounter both need to be initialized
+    // on the worker thread before being used on main-thread.
+    // Let's be sure that it is created before any
     // content loading.
     aWorkerPrivate->EnsurePerformanceStorage();
+#ifndef RELEASE_OR_BETA
+    aWorkerPrivate->EnsurePerformanceCounter();
+#endif
 
     ErrorResult rv;
     workerinternals::LoadMainScript(aWorkerPrivate, mScriptURL, WorkerScript, rv);
     rv.WouldReportJSException();
     // Explicitly ignore NS_BINDING_ABORTED on rv.  Or more precisely, still
     // return false and don't SetWorkerScriptExecutedSuccessfully() in that
     // case, but don't throw anything on aCx.  The idea is to not dispatch error
     // events if our load is canceled with that error code.
@@ -5205,25 +5209,29 @@ WorkerPrivate::DumpCrashInformation(nsAC
   while (iter.HasMore()) {
     WorkerHolder* holder = iter.GetNext();
     aString.Append("|");
     aString.Append(holder->Name());
   }
 }
 
 #ifndef RELEASE_OR_BETA
+void
+WorkerPrivate::EnsurePerformanceCounter()
+{
+  AssertIsOnWorkerThread();
+  if (!mPerformanceCounter) {
+    mPerformanceCounter = new PerformanceCounter(NS_ConvertUTF16toUTF8(mWorkerName));
+  }
+}
+
 PerformanceCounter*
 WorkerPrivate::GetPerformanceCounter()
 {
-  AssertIsOnWorkerThread();
-
-  if (!mPerformanceCounter) {
-    mPerformanceCounter = new PerformanceCounter(NS_ConvertUTF16toUTF8(mWorkerName));
-  }
-
+  MOZ_ASSERT(mPerformanceCounter);
   return mPerformanceCounter;
 }
 #endif
 
 PerformanceStorage*
 WorkerPrivate::GetPerformanceStorage()
 {
   AssertIsOnMainThread();
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -556,16 +556,21 @@ public:
   DumpCrashInformation(nsACString& aString);
 
   bool
   EnsureClientSource();
 
   void
   EnsurePerformanceStorage();
 
+#ifndef RELEASE_OR_BETA
+  void
+  EnsurePerformanceCounter();
+#endif
+
   const ClientInfo&
   GetClientInfo() const;
 
   const ClientState
   GetClientState() const;
 
   const Maybe<ServiceWorkerDescriptor>
   GetController() const;
--- a/xpcom/threads/TaskCategory.h
+++ b/xpcom/threads/TaskCategory.h
@@ -31,14 +31,17 @@ enum class TaskCategory {
   RefreshDriver,
 
   // GC/CC-related tasks
   GarbageCollection,
 
   // Most DOM events (postMessage, media, plugins)
   Other,
 
+  // Runnables related to Performance Counting
+  Performance,
+
   Count
 };
 
 } // namespace mozilla
 
 #endif // mozilla_TaskCategory_h