Bug 1447768 - part 2 - Dispatch counters in the parent process - r?baku draft
authorTarek Ziadé <tarek@mozilla.com>
Wed, 04 Apr 2018 13:36:25 +0200
changeset 777206 9a47ac09041071e35e625839b15ed90e4cf98739
parent 777205 6bc72399653f17e85ec686f7d06a4b6b24b08539
push id105106
push usertziade@mozilla.com
push dateWed, 04 Apr 2018 11:39:15 +0000
reviewersbaku
bugs1447768
milestone61.0a1
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.
@@ -5210,25 +5214,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
@@ -559,16 +559,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