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
--- 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