Bug 674779 - Per-component CPU monitoring, high-level;r=blassey,froydnj draft
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Fri, 06 Mar 2015 22:58:40 +0100
changeset 248995 82e7dcf782c0fc5f9b9f13a5b8e4c4c463268737
parent 248994 7f8398d80254da933a7e8b91de8f17743d401349
child 249715 1d283cfb7a5b2bba41dcbe89068e0df95c23f5e6
child 250408 7f5064bee40d4965cd2659415530c4b805794994
push id960
push userdteller@mozilla.com
push dateMon, 09 Mar 2015 17:52:57 +0000
reviewersblassey, froydnj
bugs674779
milestone39.0a1
Bug 674779 - Per-component CPU monitoring, high-level;r=blassey,froydnj
b2g/installer/package-manifest.in
browser/base/content/test/social/browser_addons.js
browser/installer/package-manifest.in
docshell/base/nsAboutRedirector.cpp
docshell/build/nsDocShellModule.cpp
mobile/android/installer/package-manifest.in
toolkit/components/aboutcompartments/content/aboutCompartments.js
toolkit/components/aboutcompartments/content/aboutCompartments.xhtml
toolkit/components/aboutcompartments/jar.mn
toolkit/components/aboutcompartments/moz.build
toolkit/components/aboutcompartments/nsCompartmentInfo.cpp
toolkit/components/aboutcompartments/nsCompartmentInfo.h
toolkit/components/aboutcompartments/nsICompartmentInfo.idl
toolkit/components/aboutperformance/content/aboutPerformance.js
toolkit/components/aboutperformance/content/aboutPerformance.xhtml
toolkit/components/aboutperformance/jar.mn
toolkit/components/aboutperformance/moz.build
toolkit/components/aboutperformance/nsIPerformanceStats.idl
toolkit/components/aboutperformance/nsPerformanceStats.cpp
toolkit/components/aboutperformance/nsPerformanceStats.h
toolkit/components/aboutperformance/tests/mochi/browser.ini
toolkit/components/aboutperformance/tests/mochi/browser_compartments.html
toolkit/components/aboutperformance/tests/mochi/browser_compartments.js
toolkit/components/aboutperformance/tests/mochi/content.js
toolkit/components/aboutperformance/tests/xpcshell/test_compartments.js
toolkit/components/aboutperformance/tests/xpcshell/xpcshell.ini
toolkit/components/build/moz.build
toolkit/components/build/nsToolkitCompsModule.cpp
toolkit/components/moz.build
toolkit/modules/AddonWatcher.jsm
xpcom/build/XPCOMInit.cpp
xpcom/build/XPCOMModule.inc
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -140,17 +140,16 @@
 @BINPATH@/components/browsercompsbase.xpt
 @BINPATH@/components/browser-element.xpt
 @BINPATH@/components/browser-feeds.xpt
 @BINPATH@/components/caps.xpt
 @BINPATH@/components/chardet.xpt
 @BINPATH@/components/chrome.xpt
 @BINPATH@/components/commandhandler.xpt
 @BINPATH@/components/commandlines.xpt
-@BINPATH@/components/compartments.xpt
 @BINPATH@/components/composer.xpt
 @BINPATH@/components/content_events.xpt
 @BINPATH@/components/content_html.xpt
 @BINPATH@/components/content_xslt.xpt
 @BINPATH@/components/cookie.xpt
 @BINPATH@/components/devtools_security.xpt
 @BINPATH@/components/directory.xpt
 @BINPATH@/components/diskspacewatcher.xpt
@@ -306,16 +305,17 @@
 @BINPATH@/components/spellchecker.xpt
 @BINPATH@/components/storage.xpt
 @BINPATH@/components/telemetry.xpt
 @BINPATH@/components/toolkit_asyncshutdown.xpt
 @BINPATH@/components/toolkit_filewatcher.xpt
 @BINPATH@/components/toolkit_finalizationwitness.xpt
 @BINPATH@/components/toolkit_formautofill.xpt
 @BINPATH@/components/toolkit_osfile.xpt
+@RESPATH@/components/toolkit_perfmonitoring.xpt
 @BINPATH@/components/toolkit_xulstore.xpt
 @BINPATH@/components/toolkitprofile.xpt
 #ifdef MOZ_ENABLE_XREMOTE
 @BINPATH@/components/toolkitremote.xpt
 #endif
 @BINPATH@/components/txtsvc.xpt
 @BINPATH@/components/txmgr.xpt
 #ifdef MOZ_USE_NATIVE_UCONV
--- a/browser/base/content/test/social/browser_addons.js
+++ b/browser/base/content/test/social/browser_addons.js
@@ -1,12 +1,13 @@
 
 
 let AddonManager = Cu.import("resource://gre/modules/AddonManager.jsm", {}).AddonManager;
 let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
+let AddonWatcher = Cu.import("resource://gre/modules/AddonWatcher.jsm", {}).AddonWatcher;
 
 const ADDON_TYPE_SERVICE     = "service";
 const ID_SUFFIX              = "@services.mozilla.org";
 const STRING_TYPE_NAME       = "type.%ID%.name";
 const XPINSTALL_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
 
 let manifest = {
   name: "provider 1",
@@ -22,16 +23,22 @@ let manifest2 = { // used for testing in
   workerURL: "https://test1.example.com/browser/browser/base/content/test/social/social_worker.js",
   iconURL: "https://test1.example.com/browser/browser/base/content/test/general/moz.png",
   version: 1
 };
 
 function test() {
   waitForExplicitFinish();
 
+  // Deactivating the AddonWatcher during the test
+  // (bug 1094218)
+  let isWatcherPaused = AddonWatcher.isPaused;
+  if (!isWatcherPaused) {
+    registerCleanupFunction(() => {AddonWatcher = isWatcherPaused;});
+  }
   let prefname = getManifestPrefname(manifest);
   // ensure that manifest2 is NOT showing as builtin
   is(SocialService.getOriginActivationType(manifest.origin), "foreign", "manifest is foreign");
   is(SocialService.getOriginActivationType(manifest2.origin), "foreign", "manifest2 is foreign");
 
   Services.prefs.setBoolPref("social.remote-install.enabled", true);
   runSocialTests(tests, undefined, undefined, function () {
     Services.prefs.clearUserPref("social.remote-install.enabled");
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -176,17 +176,16 @@
 @RESPATH@/components/browser-element.xpt
 @RESPATH@/browser/components/browsercompsbase.xpt
 @RESPATH@/browser/components/browser-feeds.xpt
 @RESPATH@/browser/components/browsermodules.manifest
 @RESPATH@/components/caps.xpt
 @RESPATH@/components/chrome.xpt
 @RESPATH@/components/commandhandler.xpt
 @RESPATH@/components/commandlines.xpt
-@RESPATH@/components/compartments.xpt
 @RESPATH@/components/composer.xpt
 @RESPATH@/components/content_events.xpt
 @RESPATH@/components/content_html.xpt
 @RESPATH@/components/content_geckomediaplugins.xpt
 #ifdef MOZ_WEBRTC
 @RESPATH@/components/content_webrtc.xpt
 #endif
 @RESPATH@/components/content_xslt.xpt
@@ -323,16 +322,17 @@
 @RESPATH@/components/shistory.xpt
 @RESPATH@/components/spellchecker.xpt
 @RESPATH@/components/storage.xpt
 @RESPATH@/components/toolkit_asyncshutdown.xpt
 @RESPATH@/components/toolkit_filewatcher.xpt
 @RESPATH@/components/toolkit_finalizationwitness.xpt
 @RESPATH@/components/toolkit_formautofill.xpt
 @RESPATH@/components/toolkit_osfile.xpt
+@RESPATH@/components/toolkit_perfmonitoring.xpt
 @RESPATH@/components/toolkit_xulstore.xpt
 @RESPATH@/components/toolkitprofile.xpt
 #ifdef MOZ_ENABLE_XREMOTE
 @RESPATH@/components/toolkitremote.xpt
 #endif
 @RESPATH@/components/txtsvc.xpt
 @RESPATH@/components/txmgr.xpt
 @RESPATH@/components/uconv.xpt
--- a/docshell/base/nsAboutRedirector.cpp
+++ b/docshell/base/nsAboutRedirector.cpp
@@ -67,17 +67,17 @@ static RedirEntry kRedirMap[] = {
       nsIAboutModule::ALLOW_SCRIPT |
       nsIAboutModule::HIDE_FROM_ABOUTABOUT
   },
   {
     "memory", "chrome://global/content/aboutMemory.xhtml",
     nsIAboutModule::ALLOW_SCRIPT
   },
   {
-    "compartments", "chrome://global/content/aboutCompartments.xhtml",
+    "performance", "chrome://global/content/aboutPerformance.xhtml",
     nsIAboutModule::ALLOW_SCRIPT
   },
   {
     "addons", "chrome://mozapps/content/extensions/extensions.xul",
     nsIAboutModule::ALLOW_SCRIPT
   },
   {
     "newaddon", "chrome://mozapps/content/extensions/newaddon.xul",
--- a/docshell/build/nsDocShellModule.cpp
+++ b/docshell/build/nsDocShellModule.cpp
@@ -161,19 +161,18 @@ const mozilla::Module::ContractIDEntry k
 #endif
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "credits", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "plugins", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "mozilla", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "logo", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "buildconfig", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "license", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "neterror", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
-  { NS_ABOUT_MODULE_CONTRACTID_PREFIX "compartments", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "memory", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
-  { NS_ABOUT_MODULE_CONTRACTID_PREFIX "compartments", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
+  { NS_ABOUT_MODULE_CONTRACTID_PREFIX "performance", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "addons", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "newaddon", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "support", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "telemetry", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "networking", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "webrtc", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "srcdoc", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_URI_LOADER_CONTRACTID, &kNS_URI_LOADER_CID },
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -119,17 +119,16 @@
 @BINPATH@/components/browsercompsbase.xpt
 @BINPATH@/components/browser-element.xpt
 @BINPATH@/components/browser-feeds.xpt
 @BINPATH@/components/caps.xpt
 @BINPATH@/components/chardet.xpt
 @BINPATH@/components/chrome.xpt
 @BINPATH@/components/commandhandler.xpt
 @BINPATH@/components/commandlines.xpt
-@BINPATH@/components/compartments.xpt
 @BINPATH@/components/composer.xpt
 @BINPATH@/components/content_events.xpt
 @BINPATH@/components/content_geckomediaplugins.xpt
 @BINPATH@/components/content_html.xpt
 @BINPATH@/components/content_webrtc.xpt
 @BINPATH@/components/content_xslt.xpt
 @BINPATH@/components/cookie.xpt
 @BINPATH@/components/directory.xpt
@@ -250,16 +249,17 @@
 @BINPATH@/components/spellchecker.xpt
 @BINPATH@/components/storage.xpt
 @BINPATH@/components/telemetry.xpt
 @BINPATH@/components/toolkit_asyncshutdown.xpt
 @BINPATH@/components/toolkit_filewatcher.xpt
 @BINPATH@/components/toolkit_finalizationwitness.xpt
 @BINPATH@/components/toolkit_formautofill.xpt
 @BINPATH@/components/toolkit_osfile.xpt
+@BINPATH@/components/toolkit_perfmonitoring.xpt
 @BINPATH@/components/toolkit_xulstore.xpt
 @BINPATH@/components/toolkitprofile.xpt
 #ifdef MOZ_ENABLE_XREMOTE
 @BINPATH@/components/toolkitremote.xpt
 #endif
 @BINPATH@/components/txtsvc.xpt
 @BINPATH@/components/txmgr.xpt
 @BINPATH@/components/uconv.xpt
rename from toolkit/components/aboutcompartments/content/aboutCompartments.js
rename to toolkit/components/aboutperformance/content/aboutPerformance.js
--- a/toolkit/components/aboutcompartments/content/aboutCompartments.js
+++ b/toolkit/components/aboutperformance/content/aboutPerformance.js
@@ -5,71 +5,141 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
 
-function go() {
-    let compartmentInfo = Cc["@mozilla.org/compartment-info;1"]
-            .getService(Ci.nsICompartmentInfo);
-    let compartments = compartmentInfo.getCompartments();
-    let count = compartments.length;
-    let addons = {};
-    for (let i = 0; i < count; i++) {
-        let compartment = compartments.queryElementAt(i, Ci.nsICompartment);
-        if (addons[compartment.addonId]) {
-            addons[compartment.addonId].time += compartment.time;
-            addons[compartment.addonId].CPOWTime += compartment.CPOWTime;
-            addons[compartment.addonId].compartments.push(compartment);
-        } else {
-            addons[compartment.addonId] = {
-                time: compartment.time,
-                CPOWTime: compartment.CPOWTime,
-                compartments: [compartment]
-            };
-        }
+const performanceStatsService = Cc["@mozilla.org/toolkit/performance-stats-service;1"]
+  .getService(Ci.nsIPerformanceStatsService);
+
+/**
+ * The various measures we extract from a nsICompartment.
+ */
+const MEASURES = [
+  {key: "totalUserTime", label: "Total user (µs)"},
+  {key: "totalSystemTime", label: "Total system (µs)"},
+  {key: "totalCPOWTime", label: "CPOW (µs)"},
+  {key: "ticks", label: "Activations"},
+];
+
+/**
+ * All information on a single owner (add-on, page, built-ins).
+ *
+ * @param {string} id A unique identifier.
+ * @param {string} kind One of "<addon>", "<built-in>", "<page>".
+ * @param {string|null} A name for this owner. Not expected for add-ons.
+ */
+function PerformancePrincipal(id, kind, name) {
+  this.measures = {};
+  this.id = id;
+  this.kind = kind;
+  this.name = name;
+  for (let {key} of MEASURES) {
+    this.measures[key] = 0;
+  }
+  this.measures.missedFrames = [];
+}
+PerformancePrincipal.prototype = {
+  promiseName: function() {
+    if (this.kind != "<addon>") {
+      return Promise.resolve(this.name);
     }
-    let dataDiv = document.getElementById("data");
-    for (let addon in addons) {
-        let el = document.createElement("tr");
-        let name = document.createElement("td");
-        let time = document.createElement("td");
-        let cpow = document.createElement("td");
-        name.className = "addon";
-        time.className = "time";
-        cpow.className = "cpow";
-        name.textContent = addon;
-        AddonManager.getAddonByID(addon, function(a) {
-            if (a) {
-                name.textContent = a.name;
-            }
-        });
-        time.textContent = addons[addon].time +"μs";
-        cpow.textContent = addons[addon].CPOWTime +"μs";
-        el.appendChild(time);
-        el.appendChild(cpow);
-        el.appendChild(name);
-        let div = document.createElement("tr");
-        for (let comp of addons[addon].compartments) {
-            let c = document.createElement("tr");
-            let name = document.createElement("td");
-            let time = document.createElement("td");
-            let cpow = document.createElement("td");
-            name.className = "addon";
-            time.className = "time";
-            cpow.className = "cpow";
-            name.textContent = comp.compartmentName;
-            time.textContent = comp.time +"μs";
-            cpow.textContent = comp.CPOWTime +"μs";
-            c.appendChild(time);
-            c.appendChild(cpow);
-            c.appendChild(name);
-            div.appendChild(c);
-            div.className = "details";
-        }
-        el.addEventListener("click", function() { div.style.display = (div.style.display != "block" ? "block" : "none"); });
-        el.appendChild(div);
-        dataDiv.appendChild(el);
+    return new Promise(resolve =>
+      AddonManager.getAddonByID(this.id, a =>
+        resolve(a?a.name:null)
+      )
+    );
+  }
+};
+
+function exportStatistics(performanceData) {
+  let id, kind, name;
+  if (!performanceData.isSystem) {
+    name = id = performanceData.name;
+    kind = "<page>";
+  } else if (!performanceData.addonId) {
+    id = kind = name = "<built-in>";
+  } else {
+    name = id = performanceData.addonId;
+    kind = "<addon>";
+  }
+  let result = new PerformancePrincipal(id, kind, name);
+  for (let {key} of MEASURES) {
+    result.measures[key] = performanceData[key];
+  }
+  for (let i = 0; i < performanceData.MISSED_FRAME_RANGE; ++i) {
+    result.missedFrames[i] = performanceData.getMissedFrames[i];
+  }
+  return result;
+}
+
+function getStatistics() {
+  let snapshot = performanceStatsService.snapshot;
+  let result = {
+    process: exportStatistics(snapshot.getProcessData()),
+    components: [exportStatistics(item) for (item of snapshot.getComponentsData())]
+  };
+  result.components.sort((a, b) => a.totalUserTime <= b.totalUserTime);
+  return result;
+}
+
+function update() {
+  try {
+    // Activate (or reactivate) monitoring
+    performanceStatsService.isStopwatchActive = true;
+
+    let dataElt = document.getElementById("data");
+    dataElt.innerHTML = "";
+
+    // Generate table headers
+    let headerElt = document.createElement("tr");
+    dataElt.appendChild(headerElt);
+    for (let column of [...MEASURES, {key: "name", name: ""}]) {
+      let el = document.createElement("td");
+      el.classList.add(column.key);
+      el.classList.add("header");
+      el.textContent = column.label;
+      headerElt.appendChild(el);
     }
+
+    // Generate table contents
+    let stats = getStatistics();
+    for (let item of stats.components) {
+      let row = document.createElement("tr");
+      row.classList.add(item.kind);
+      dataElt.appendChild(row);
+
+      // Measures
+      for (let column of MEASURES) {
+        let el = document.createElement("td");
+        el.classList.add(column.key);
+        el.classList.add("contents");
+        el.textContent = item.measures[column.key];
+        row.appendChild(el);
+      }
+
+      // Name
+      let el = document.createElement("td");
+      let id = item.id;
+      el.classList.add("contents");
+      el.classList.add("name");
+      row.appendChild(el);
+      item.promiseName().then(name => {
+        el.textContent = name || id;
+      });
+    }
+  } catch (ex) {
+    console.error(ex);
+  }
 }
+
+function stop() {
+  performanceStatsService.isStopwatchActive = false;
+}
+
+function go() {
+  update();
+  window.setInterval(update, 5000);
+  window.addEventListener("beforeunload", stop);
+}
rename from toolkit/components/aboutcompartments/content/aboutCompartments.xhtml
rename to toolkit/components/aboutperformance/content/aboutPerformance.xhtml
--- a/toolkit/components/aboutcompartments/content/aboutCompartments.xhtml
+++ b/toolkit/components/aboutperformance/content/aboutPerformance.xhtml
@@ -1,18 +1,18 @@
 <?xml version="1.0"?>
 
 <!-- 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/. -->
 
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
-    <title>about:compartments</title>
-    <script type="text/javascript;version=1.8" src="chrome://global/content/aboutCompartments.js"></script>
+    <title>about:performance</title>
+    <script type="text/javascript;version=1.8" src="chrome://global/content/aboutPerformance.js"></script>
     <style>
       td.addon {
         display: inline-block;
         width: 400px;
       }
       td.time {
         display: inline-block;
         width: 100px;
@@ -24,20 +24,24 @@
       .header {
         font-weight: bold;
       }
       tr.details {
         font-weight: lighter;
         color: gray;
         display: none;
       }
+      tr.addons {
+        background-color: white;
+      }
+      tr.builtins {
+        background-color: rgb(1, 1, .5);
+      }
+      tr.pages {
+        background-color: rgb(.5, 1, 1);
+      }
     </style>
   </head>
   <body onload="go()">
     <table id="data">
-      <tr class="header">
-        <td class="time">time</td>
-        <td class="cpow">time in CPOWs</td>
-        <td class="addon">name</td>
-      </tr>
     </table>
   </body>
 </html>
rename from toolkit/components/aboutcompartments/jar.mn
rename to toolkit/components/aboutperformance/jar.mn
--- a/toolkit/components/aboutcompartments/jar.mn
+++ b/toolkit/components/aboutperformance/jar.mn
@@ -1,7 +1,7 @@
 # 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/.
 
 toolkit.jar:
-+ content/global/aboutCompartments.xhtml               (content/aboutCompartments.xhtml)
-+ content/global/aboutCompartments.js                  (content/aboutCompartments.js)
++ content/global/aboutPerformance.xhtml               (content/aboutPerformance.xhtml)
++ content/global/aboutPerformance.js                  (content/aboutPerformance.js)
rename from toolkit/components/aboutcompartments/moz.build
rename to toolkit/components/aboutperformance/moz.build
--- a/toolkit/components/aboutcompartments/moz.build
+++ b/toolkit/components/aboutperformance/moz.build
@@ -1,23 +1,28 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
+FAIL_ON_WARNINGS = True
+
 JAR_MANIFESTS += ['jar.mn']
 
-XPIDL_MODULE = 'compartments'
+XPIDL_MODULE = 'toolkit_perfmonitoring'
 
 XPIDL_SOURCES += [
-    'nsICompartmentInfo.idl',
+    'nsIPerformanceStats.idl',
 ]
 
 UNIFIED_SOURCES += [
-    'nsCompartmentInfo.cpp'
+    'nsPerformanceStats.cpp'
 ]
 
 EXPORTS += [
-    'nsCompartmentInfo.h'
+    'nsPerformanceStats.h'
 ]
 
+BROWSER_CHROME_MANIFESTS += ['tests/mochi/browser.ini']
+XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
+
 FINAL_LIBRARY = 'xul'
rename from toolkit/components/aboutcompartments/nsICompartmentInfo.idl
rename to toolkit/components/aboutperformance/nsIPerformanceStats.idl
--- a/toolkit/components/aboutcompartments/nsICompartmentInfo.idl
+++ b/toolkit/components/aboutperformance/nsIPerformanceStats.idl
@@ -2,30 +2,102 @@
 /* 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"
 
-[scriptable, uuid(13dd4c09-ff11-4943-8dc2-d96eb69c963b)]
-interface nsICompartment : nsISupports {
-  /* name of compartment */
-  readonly attribute AString compartmentName;
-  /* time spent executing code in this compartment in microseconds */
-  readonly attribute unsigned long long time;
-  /* the id of the addon associated with this compartment, or null */
+/**
+ * Mechanisms for querying the current process about performance
+ * information.
+ */
+
+/**
+ * Performance statistics about a component, e.g. an add-on, a web
+ * page, system built-ins, or the entire process itself.
+ *
+ * All values are monotonic and are updated only when
+ * `nsIPerformanceStatsService.isStopwatchActive` is `true`.
+ */
+[scriptable, uuid(f015cbad-e16f-4982-a080-67e4e69a5b2e)]
+interface nsIPerformanceStats: nsISupports {
+  /**
+   * If the component is a webpage, the process itself or a special
+   * compartment, a human-readable name. Unspecified for add-ons.
+   */
+  readonly attribute AString name;
+
+  /**
+   * If the component is an add-on, the ID of the addon,
+   * otherwise an empty string.
+   */
   readonly attribute AString addonId;
-  /* time spent processing CPOWs in microseconds */
-  readonly attribute unsigned long long CPOWTime;
+
+  /**
+   * Total amount of time spent executing code in this group, in
+   * microseconds.
+   */
+  readonly attribute unsigned long long totalUserTime;
+  readonly attribute unsigned long long totalSystemTime;
+  readonly attribute unsigned long long totalCPOWTime;
+
+  /**
+   * Total number of times code execution entered this group,
+   * since process launch. This may be greater than the number
+   * of times we have entered the event loop.
+   */
+  readonly attribute unsigned long long ticks;
+
+  /**
+   * `true` if the component consists in system compartments
+   * (e.g. add-ons, internals), `false` otherwise. A component may
+   * never contain both system and non-system compartments.
+   */
+  readonly attribute bool isSystem;
+
+  /**
+   * Jank indicator.
+   *
+   * missedFrames[i] == number of times execution of this group
+   * caused us to miss at least 2^i successive frames - we assume
+   * that a frame lasts 16ms.
+   */
+  void getMissedFrames([optional] out unsigned long aCount,
+                       [retval, array, size_is(aCount)]out unsigned long long aNumberOfOccurrences);
+};
+
+/**
+ * A snapshot of the performance data of the process.
+ */
+[scriptable, uuid(29ecebd0-908a-4b34-8f62-a6015dea1141)]
+interface nsIPerformanceSnapshot: nsISupports {
+  /**
+   * Data on all individual components.
+   */
+  void getComponentsData([optional] out unsigned long aCount,
+                         [retval, array, size_is(aCount)] out nsIPerformanceStats aComponents);
+
+  /**
+   * Information on the process itself.
+   */
+  nsIPerformanceStats getProcessData();
 };
 
 [scriptable, builtinclass, uuid(5795113a-39a1-4087-ba09-98b7d07d025a)]
-interface nsICompartmentInfo : nsISupports {
-  nsIArray getCompartments();
+interface nsIPerformanceStatsService : nsISupports {
+  /**
+   * `true` if we should monitor performance, `false` otherwise.
+   */
+  [implicit_jscontext] attribute bool isStopwatchActive;
+
+  /**
+   * Capture a snapshot of the performance data.
+   */
+  readonly attribute nsIPerformanceSnapshot snapshot;
 };
 
 %{C++
-#define NS_COMPARTMENT_INFO_CID \
-{ 0x2d3c2f2d, 0x698d, 0x471d, \
-{ 0xba, 0x3e, 0x14, 0x44, 0xdd, 0x52, 0x1e, 0x29 } }
+#define NS_TOOLKIT_PERFORMANCESTATSSERVICE_CID {0xfd7435d4, 0x9ec4, 0x4699, \
+      {0xad, 0xd4, 0x1b, 0xe8, 0x3d, 0xd6, 0x8e, 0xf3} }
+#define NS_TOOLKIT_PERFORMANCESTATSSERVICE_CONTRACTID "@mozilla.org/toolkit/performance-stats-service;1"
 %}
rename from toolkit/components/aboutcompartments/nsCompartmentInfo.cpp
rename to toolkit/components/aboutperformance/nsPerformanceStats.cpp
--- a/toolkit/components/aboutcompartments/nsCompartmentInfo.cpp
+++ b/toolkit/components/aboutperformance/nsPerformanceStats.cpp
@@ -1,94 +1,223 @@
 /* -*- 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 "nsCompartmentInfo.h"
+#include "jsapi.h"
+#include "nsPerformanceStats.h"
 #include "nsMemory.h"
 #include "nsLiteralString.h"
 #include "nsCRTGlue.h"
 #include "nsIJSRuntimeService.h"
 #include "nsServiceManagerUtils.h"
-#include "nsIMutableArray.h"
+#include "nsCOMArray.h"
 #include "nsJSUtils.h"
 #include "xpcpublic.h"
+#include "jspubtd.h"
 
-class nsCompartment : public nsICompartment {
+class nsPerformanceStats: public nsIPerformanceStats {
 public:
-  nsCompartment(nsAString& aCompartmentName, nsAString& aAddonId,
-                uint64_t aTime, uint64_t aCPOWTime)
-    : mCompartmentName(aCompartmentName), mAddonId(aAddonId), mTime(aTime), mCPOWTime(aCPOWTime) {}
+  nsPerformanceStats(nsAString& aName, nsAString& aAddonId, bool aIsSystem, js::PerformanceData& aPerformanceData)
+    : mName(aName)
+    , mAddonId(aAddonId)
+    , mIsSystem(aIsSystem)
+    , mPerformanceData(aPerformanceData)
+  {}
 
   NS_DECL_ISUPPORTS
 
-  /* readonly attribute wstring compartmentName; */
-  NS_IMETHOD GetCompartmentName(nsAString& aCompartmentName) MOZ_OVERRIDE {
-    aCompartmentName.Assign(mCompartmentName);
+  /* readonly attribute AString name; */
+  NS_IMETHOD GetName(nsAString& aName) MOZ_OVERRIDE {
+    aName.Assign(mName);
     return NS_OK;
   };
 
-  /* readonly attribute unsigned long time; */
-  NS_IMETHOD GetTime(uint64_t* aTime) MOZ_OVERRIDE {
-    *aTime = mTime;
-    return NS_OK;
-  }
-  /* readonly attribute wstring addon id; */
+  /* readonly attribute AString addon id; */
   NS_IMETHOD GetAddonId(nsAString& aAddonId) MOZ_OVERRIDE {
     aAddonId.Assign(mAddonId);
     return NS_OK;
   };
 
-  /* readonly attribute unsigned long CPOW time; */
-  NS_IMETHOD GetCPOWTime(uint64_t* aCPOWTime) MOZ_OVERRIDE {
-    *aCPOWTime = mCPOWTime;
+  /* readonly attribute unsigned long long totalUserTime; */
+  NS_IMETHOD GetTotalUserTime(uint64_t *aTotalUserTime) {
+    *aTotalUserTime = mPerformanceData.totalUserTime;
+    return NS_OK;
+  };
+
+  /* readonly attribute unsigned long long totalSystemTime; */
+  NS_IMETHOD GetTotalSystemTime(uint64_t *aTotalSystemTime) {
+    *aTotalSystemTime = mPerformanceData.totalSystemTime;
+    return NS_OK;
+  };
+
+  /* readonly attribute unsigned long long totalCPOWTime; */
+  NS_IMETHOD GetTotalCPOWTime(uint64_t *aCpowTime) {
+    *aCpowTime = mPerformanceData.totalCPOWTime;
+    return NS_OK;
+  };
+
+  /* readonly attribute unsigned long long ticks; */
+  NS_IMETHOD GetTicks(uint64_t *aTicks) {
+    *aTicks = mPerformanceData.ticks;
+    return NS_OK;
+  };
+
+  /* void getMissedFrames (out unsigned long aCount, [array, size_is (aCount), retval] out unsigned long long aNumberOfOccurrences); */
+  NS_IMETHODIMP GetMissedFrames(uint32_t *aCount, uint64_t **aNumberOfOccurrences) {
+    const size_t length = mozilla::ArrayLength(mPerformanceData.missedFrames);
+    if (aCount) {
+      *aCount = length;
+    }
+    *aNumberOfOccurrences = static_cast<uint64_t*>
+      (nsMemory::Alloc(sizeof(uint64_t*) * length));
+    for (size_t i = 0; i < length; ++i) {
+      (*aNumberOfOccurrences)[i] = mPerformanceData.missedFrames[i];
+    }
+    return NS_OK;
+  };
+
+  /* readonly attribute bool isSystem; */
+  NS_IMETHOD GetIsSystem(bool *_retval) {
+    *_retval = mIsSystem;
     return NS_OK;
   }
 
 private:
-  nsString mCompartmentName;
+  nsString mName;
   nsString mAddonId;
-  uint64_t mTime;
-  uint64_t mCPOWTime;
-  virtual ~nsCompartment() {}
+  bool mIsSystem;
+  js::PerformanceData mPerformanceData;
+
+  virtual ~nsPerformanceStats() {}
 };
 
-NS_IMPL_ISUPPORTS(nsCompartment, nsICompartment)
-NS_IMPL_ISUPPORTS(nsCompartmentInfo, nsICompartmentInfo)
+NS_IMPL_ISUPPORTS(nsPerformanceStats, nsIPerformanceStats)
+
+
+class nsPerformanceSnapshot : public nsIPerformanceSnapshot
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIPERFORMANCESNAPSHOT
 
-nsCompartmentInfo::nsCompartmentInfo()
+  nsPerformanceSnapshot();
+  nsresult Init();
+private:
+  virtual ~nsPerformanceSnapshot();
+
+  /**
+   * Import a `PerformanceStats` as a `nsIPerformanceStats`.
+   */
+  already_AddRefed<nsIPerformanceStats> ImportStats(PerformanceStats* c);
+
+  nsCOMArray<nsIPerformanceStats> mComponentsData;
+  nsCOMPtr<nsIPerformanceStats> mProcessData;
+};
+
+NS_IMPL_ISUPPORTS(nsPerformanceSnapshot, nsIPerformanceSnapshot)
+
+nsPerformanceSnapshot::nsPerformanceSnapshot()
 {
 }
 
-nsCompartmentInfo::~nsCompartmentInfo()
+nsPerformanceSnapshot::~nsPerformanceSnapshot()
 {
 }
 
-NS_IMETHODIMP
-nsCompartmentInfo::GetCompartments(nsIArray** aCompartments)
-{
+already_AddRefed<nsIPerformanceStats>
+nsPerformanceSnapshot::ImportStats(PerformanceStats* c) {
+  nsString addonId;
+  if (c->addonId) {
+    AssignJSFlatString(addonId, (JSFlatString*)c->addonId);
+  }
+  nsCString cname(c->name);
+  NS_ConvertUTF8toUTF16 name(cname);
+  nsCOMPtr<nsIPerformanceStats> result = new nsPerformanceStats(name, addonId, c->isSystem, c->performance);
+  return result.forget();
+}
+
+nsresult
+nsPerformanceSnapshot::Init() {
   JSRuntime* rt;
   nsCOMPtr<nsIJSRuntimeService> svc(do_GetService("@mozilla.org/js/xpc/RuntimeService;1"));
   NS_ENSURE_TRUE(svc, NS_ERROR_FAILURE);
   svc->GetRuntime(&rt);
-  nsCOMPtr<nsIMutableArray> compartments = do_CreateInstance(NS_ARRAY_CONTRACTID);
-  CompartmentStatsVector stats;
-  if (!JS_GetCompartmentStats(rt, stats))
+  PerformanceStats processStats;
+  PerformanceStatsVector componentsStats;
+  if (!JS_GetPerformanceStats(rt, componentsStats, processStats)) {
     return NS_ERROR_OUT_OF_MEMORY;
+  }
 
-  size_t num = stats.length();
+  size_t num = componentsStats.length();
   for (size_t pos = 0; pos < num; pos++) {
-    nsString addonId;
-    if (stats[pos].addonId) {
-      AssignJSFlatString(addonId, (JSFlatString*)stats[pos].addonId);
-    } else {
-      addonId.AssignLiteral("<non-addon>");
-    }
+    nsCOMPtr<nsIPerformanceStats> stats = ImportStats(&componentsStats[pos]);
+    mComponentsData.AppendObject(stats);
+  }
+  mProcessData = ImportStats(&processStats);
+  return NS_OK;
+}
+
 
-    uint32_t cpowTime = xpc::GetCompartmentCPOWMicroseconds(stats[pos].compartment);
-    nsCString compartmentName(stats[pos].compartmentName);
-    NS_ConvertUTF8toUTF16 name(compartmentName);
-    compartments->AppendElement(new nsCompartment(name, addonId, stats[pos].time, cpowTime), false);
+/* void getComponentsData ([optional] out unsigned long aCount, [array, size_is (aCount), retval] out nsIPerformanceStats aComponents); */
+NS_IMETHODIMP nsPerformanceSnapshot::GetComponentsData(uint32_t *aCount, nsIPerformanceStats * **aComponents)
+{
+  const size_t length = mComponentsData.Length();
+  nsIPerformanceStats** result = static_cast<nsIPerformanceStats**>
+    (nsMemory::Alloc(sizeof(nsIPerformanceStats*) * length));
+  for (size_t i = 0; i < length; ++i) {
+    NS_ADDREF(result[i] = mComponentsData[i]);
   }
-  compartments.forget(aCompartments);
+
+  if (aCount) {
+    *aCount = length;
+  }
+  *aComponents = result;
+  return NS_OK;
+}
+
+/* readonly attribute nsIPerformanceStats process; */
+NS_IMETHODIMP nsPerformanceSnapshot::GetProcessData(nsIPerformanceStats * *aProcess)
+{
+  NS_IF_ADDREF(*aProcess = mProcessData);
   return NS_OK;
 }
+
+
+NS_IMPL_ISUPPORTS(nsPerformanceStatsService, nsIPerformanceStatsService)
+
+nsPerformanceStatsService::nsPerformanceStatsService()
+{
+}
+
+nsPerformanceStatsService::~nsPerformanceStatsService()
+{
+}
+
+/* [implicit_jscontext] attribute bool isStopwatchActive; */
+NS_IMETHODIMP nsPerformanceStatsService::GetIsStopwatchActive(JSContext* cx, bool *aIsStopwatchActive)
+{
+  JSRuntime *runtime = JS_GetRuntime(cx);
+  *aIsStopwatchActive = js::IsStopwatchActive(runtime);
+  return NS_OK;
+}
+NS_IMETHODIMP nsPerformanceStatsService::SetIsStopwatchActive(JSContext* cx, bool aIsStopwatchActive)
+{
+  JSRuntime *runtime = JS_GetRuntime(cx);
+  js::SetStopwatchActive(runtime, aIsStopwatchActive);
+  return NS_OK;
+}
+
+/* readonly attribute nsIPerformanceSnapshot snapshot; */
+NS_IMETHODIMP nsPerformanceStatsService::GetSnapshot(nsIPerformanceSnapshot * *aSnapshot)
+{
+  nsRefPtr<nsPerformanceSnapshot> snapshot = new nsPerformanceSnapshot();
+  nsresult rv = snapshot->Init();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  snapshot.forget(aSnapshot);
+  return NS_OK;
+}
+
+
rename from toolkit/components/aboutcompartments/nsCompartmentInfo.h
rename to toolkit/components/aboutperformance/nsPerformanceStats.h
--- a/toolkit/components/aboutcompartments/nsCompartmentInfo.h
+++ b/toolkit/components/aboutperformance/nsPerformanceStats.h
@@ -1,25 +1,25 @@
 /* -*- 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/. */
 
-#ifndef nsCompartmentInfo_h
-#define nsCompartmentInfo_h
+#ifndef nsPerformanceStats_h
+#define nsPerformanceStats_h
 
-#include "nsICompartmentInfo.h"
+#include "nsIPerformanceStats.h"
 
-class nsCompartmentInfo : public nsICompartmentInfo
+class nsPerformanceStatsService : public nsIPerformanceStatsService
 {
 public:
   NS_DECL_ISUPPORTS
-  NS_DECL_NSICOMPARTMENTINFO
+  NS_DECL_NSIPERFORMANCESTATSSERVICE
 
-  nsCompartmentInfo();
+  nsPerformanceStatsService();
 
 private:
-  virtual ~nsCompartmentInfo();
+  virtual ~nsPerformanceStatsService();
 
 protected:
 };
 
 #endif
new file mode 100644
--- /dev/null
+++ b/toolkit/components/aboutperformance/tests/mochi/browser.ini
@@ -0,0 +1,10 @@
+# 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/.
+
+[DEFAULT]
+support-files =
+  browser_compartments.html
+  content.js
+
+[browser_compartments.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/aboutperformance/tests/mochi/browser_compartments.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>
+    browser_compartments.html
+  </title>
+  <script type="text/javascript">
+  // Use some CPU.
+  window.setInterval(() => {
+    // Compute an arbitrary value, print it out to make sure that the JS
+    // engine doesn't discard all our computation.
+    var date = Date.now();
+    var array = [];
+    var i = 0;
+    while (Date.now() - date <= 100) {
+      array[i%2] = i++;
+    }
+    console.log("Arbitrary value", array);
+  }, 300);
+  </script>
+</head>
+<body>
+browser_compartments.html
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/aboutperformance/tests/mochi/browser_compartments.js
@@ -0,0 +1,113 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+
+const ROOT = getRootDirectory(gTestPath);
+const FRAME_SCRIPTS = [
+  ROOT + "content.js",
+];
+const URL = "http://example.com/browser/toolkit/components/aboutperformance/tests/mochi/browser_compartments.html";
+
+let mm = Cc["@mozilla.org/globalmessagemanager;1"]
+           .getService(Ci.nsIMessageListenerManager);
+let performanceStatsService = Cc["@mozilla.org/toolkit/performance-stats-service;1"]
+ .getService(Ci.nsIPerformanceStatsService);
+
+for (let script of FRAME_SCRIPTS) {
+  mm.loadFrameScript(script, true);
+}
+
+registerCleanupFunction(() => {
+  for (let script of FRAME_SCRIPTS) {
+    mm.removeDelayedFrameScript(script, true);
+  }
+});
+
+function promiseContentResponse(browser, name, message) {
+  let mm = browser.messageManager;
+  let promise = new Promise(resolve => {
+    function removeListener() {
+      mm.removeMessageListener(name, listener);
+    }
+
+    function listener(msg) {
+      removeListener();
+      resolve(msg.data);
+    }
+
+    mm.addMessageListener(name, listener);
+    registerCleanupFunction(removeListener);
+  });
+  mm.sendAsyncMessage(name, message);
+  return promise;
+}
+
+function getStatistics() {
+  return Cc["@mozilla.org/toolkit/performance-provider;1"]
+    .getService(Ci.nsIPerformanceProvider)
+    .snapshot;
+}
+
+function* promiseTabLoadEvent(tab, url)
+{
+  return new Promise(function (resolve, reject) {
+    function handleLoadEvent(event) {
+      if (event.originalTarget != tab.linkedBrowser.contentDocument ||
+          event.target.location.href == "about:blank" ||
+          (url && event.target.location.href != url)) {
+        return;
+      }
+
+      tab.linkedBrowser.removeEventListener("load", handleLoadEvent, true);
+      resolve(event);
+    }
+
+    tab.linkedBrowser.addEventListener("load", handleLoadEvent, true, true);
+    if (url)
+      tab.linkedBrowser.loadURI(url);
+  });
+}
+
+add_task(function* init() {
+  info("Initializing");
+  let monitoring = performanceStatsService.isStopwatchActive;
+  performanceStatsService.isStopwatchActive = true;
+  registerCleanupFunction(() => {
+    info("Cleaning up");
+    performanceStatsService.isStopwatchActive = monitoring;
+  });
+});
+
+add_task(function* test() {
+  info("Extracting initial state");
+  let stats0 = getStatistics();
+  Assert.notEqual(stats0.getComponentsData().length, 0, "There is more than one component");
+  Assert.ok(!stats0.getComponentsData().find(stat => stat.name.indexOf(URL) != -1),
+    "The url doesn't appear yet");
+
+  let newTab = gBrowser.addTab();
+  let browser = newTab.linkedBrowser;
+  // Setup monitoring in the tab
+  info("Setting up monitoring in the tab");
+  let childMonitoring = yield promiseContentResponse(browser, "compartments-test:setMonitoring", true);
+
+  newTab.linkedBrowser.loadURI(URL);
+  while (true) {
+    let stats = yield promiseContentResponse(browser, "compartments-test:getStatistics", null);
+    info(JSON.stringify(stats, null, "\t"));
+    let found = stats.componentStats.find(stat => {
+      return (stat.name.indexOf(URL) != -1)
+      && (stat.totalUserTime > 1000)
+    });
+    if (found) {
+      break;
+    }
+    yield new Promise(resolve => setTimeout(resolve, 100));
+  }
+
+  // Cleanup
+  yield promiseContentResponse(browser, "compartments-test:setMonitoring", childMonitoring);
+  gBrowser.removeTab(newTab);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/aboutperformance/tests/mochi/content.js
@@ -0,0 +1,38 @@
+/* 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";
+
+const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
+
+let performanceStatsService = Cc["@mozilla.org/toolkit/performance-stats-service;1"]
+        .getService(Ci.nsIPerformanceStatsService);
+
+addMessageListener("compartments-test:setMonitoring", msg => {
+  let stopwatchMonitoring = performanceStatsService.isStopwatchActive;
+  performanceStatsService.isStopwatchActive = msg.data;
+  sendAsyncMessage("compartments-test:setMonitoring", stopwatchMonitoring);
+});
+
+addMessageListener("compartments-test:getStatistics", () => {
+  try {
+    let stats = performanceStatsService.snapshot;
+    let normalize = function(xpcom) {
+      let object = {};
+      for (let k of Object.keys(xpcom)) {
+        object[k] = xpcom[k];
+      }
+      return object;
+    };
+
+    let result = {
+      processStats: normalize(stats.getProcessData()),
+      componentStats: [normalize(stat) for (stat of stats.getComponentsData())]
+    };
+    sendAsyncMessage("compartments-test:getStatistics", result);
+  } catch (ex) {
+    Cu.reportError("Error in content: " + ex);
+    Cu.reportError(ex.stack);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/aboutperformance/tests/xpcshell/test_compartments.js
@@ -0,0 +1,97 @@
+"use strict";
+
+const {utils: Cu, interfaces: Ci, classes: Cc} = Components;
+
+function run_test() {
+  run_next_test();
+}
+
+let Imports = {};
+
+let promiseStatistics = Task.async(function*(name) {
+  yield Promise.resolve(); // Make sure that we wait until
+  // statistics have been updated.
+  let snapshot = Cc["@mozilla.org/toolkit/performance-stats-service;1"]
+          .getService(Ci.nsIPerformanceStatsService)
+          .snapshot;
+  do_print("Statistics: " + name);
+  do_print(JSON.stringify(snapshot.getProcessData(), null, "\t"));
+  do_print(JSON.stringify(snapshot.getComponentsData(), null, "\t"));
+  return snapshot;
+});
+
+let promiseSetMonitoring = Task.async(function*(to) {
+  performanceStatsService.isStopwatchActive = to;
+  yield Promise.resolve();
+});
+
+function getBuiltinStatistics(snapshot) {
+  let stats = snapshot.getComponentsData().find(stats =>
+    stats.isSystem && !stats.addonId
+  );
+  Assert.notEqual(stats, null, "Found the statistics for built-ins");
+  return stats;
+}
+
+function burnCPU(ms) {
+  do_print("Burning CPU");
+  let counter = 0;
+  let ignored = [];
+  let start = Date.now();
+  while (Date.now() - start < ms) {
+    ignored.push(0);
+    ignored.shift();
+    ++counter;
+  }
+  do_print("Burning CPU over, after " + counter + " iterations");
+}
+
+function ensureEquals(snap1, snap2, name) {
+  Assert.equal(
+    JSON.stringify(snap1.getProcessData()),
+    JSON.stringify(snap2.getProcessData()),
+    "Same process data: " + name);
+  let stats1 = snap1.getComponentsData().sort((a, b) => a.name <= b.name);
+  let stats2 = snap2.getComponentsData().sort((a, b) => a.name <= b.name);
+  Assert.equal(
+    JSON.stringify(stats1),
+    JSON.stringify(stats2),
+    "Same components data: " + name
+  );
+}
+
+add_task(function* test_measure() {
+  do_print("Burn CPU without the stopwatch");
+  yield promiseSetMonitoring(false);
+  let stats0 = yield promiseStatistics("Initial state");
+  burnCPU(300);
+  let stats1 = yield promiseStatistics("Initial state + burn, without stopwatch");
+
+  do_print("Burn CPU with the stopwatch");
+  yield promiseSetMonitoring(true);
+  burnCPU(300);
+  let stats2 = yield promiseStatistics("Second burn, with stopwatch");
+
+  do_print("Burn CPU without the stopwatch again")
+  yield promiseSetMonitoring(false);
+  burnCPU(300);
+  let stats3 = yield promiseStatistics("Third burn, without stopwatch");
+
+  ensureEquals(stats0, stats1, "Initial state vs. Initial state + burn, without stopwatch");
+  Assert.ok(stats2.getProcessData().totalUserTime - stats1.getProcessData().totalUserTime >= 10000, "At least 10ms counted for process time");
+  Assert.ok(getBuiltinStatistics(stats2).totalUserTime - getBuiltinStatistics(stats1).totalUserTime >= 10000, "At least 10ms counted for built-in statistics");
+
+/*
+  Assert.equal(deltaProcess("totalUserTime", stats0, stats1), 0, "without stopwatch, process totalUserTime didn't change");
+  Assert.equal(deltaBuiltin("totalUserTime", stats0, stats1), 0, "without stopwatch, built-in totalUserTime didn't change");
+
+  Assert.ok(stats1.getProcessData().totalUserTime - stats0.getProcessData().totalUserTime > 1000, "process totalUserTime records at least 1ms of activity");
+
+  let builtins0 = getBuiltinStatistics(stats0);
+  let builtins1 = getBuiltinStatistics(stats0);
+
+  do_print("Builtins before: " + JSON.stringify(builtins0, null, "\t"));
+  do_print("Builtins after: " + JSON.stringify(builtins1, null, "\t"));
+  Assert.ok(builtins1.totalUserTime - builtins0.totalUserTime > 1000, "built-ins totalUserTime records at least 1ms of activity");
+*/
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/aboutperformance/tests/xpcshell/xpcshell.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+head=
+tail=
+
+[test_compartments.js]
--- a/toolkit/components/build/moz.build
+++ b/toolkit/components/build/moz.build
@@ -11,16 +11,17 @@ EXPORTS += [
 SOURCES += [
     'nsToolkitCompsModule.cpp',
 ]
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
     '../../xre',
+    '../aboutperformance',
     '../alerts',
     '../downloads',
     '../feeds',
     '../find',
     '../jsdownloads/src',
     '../protobuf',
     '../startup',
     '../statusfilter',
--- a/toolkit/components/build/nsToolkitCompsModule.cpp
+++ b/toolkit/components/build/nsToolkitCompsModule.cpp
@@ -46,22 +46,26 @@
 #if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
 #define MOZ_HAS_TERMINATOR
 #endif
 
 #if defined(MOZ_HAS_TERMINATOR)
 #include "nsTerminator.h"
 #endif
 
+#include "nsPerformanceStats.h"
+
 using namespace mozilla;
 
 /////////////////////////////////////////////////////////////////////////////
 
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsAppStartup, Init)
 
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsPerformanceStatsService)
+
 #if defined(MOZ_HAS_TERMINATOR)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsTerminator)
 #endif
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsUserInfo)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsFindService)
 
 #if !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
@@ -110,16 +114,17 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsUpdateP
 #endif
 NS_GENERIC_FACTORY_CONSTRUCTOR(FinalizationWitnessService)
 NS_GENERIC_FACTORY_CONSTRUCTOR(NativeOSFileInternalsService)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(NativeFileWatcherService, Init)
 
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(AddonPathService, AddonPathService::GetInstance)
 
 NS_DEFINE_NAMED_CID(NS_TOOLKIT_APPSTARTUP_CID);
+NS_DEFINE_NAMED_CID(NS_TOOLKIT_PERFORMANCESTATSSERVICE_CID);
 #if defined(MOZ_HAS_TERMINATOR)
 NS_DEFINE_NAMED_CID(NS_TOOLKIT_TERMINATOR_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_USERINFO_CID);
 NS_DEFINE_NAMED_CID(NS_ALERTSSERVICE_CID);
 #if !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
 NS_DEFINE_NAMED_CID(NS_PARENTALCONTROLSSERVICE_CID);
 #endif
@@ -144,16 +149,17 @@ NS_DEFINE_NAMED_CID(NATIVE_OSFILE_INTERN
 NS_DEFINE_NAMED_CID(NS_ADDON_PATH_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NATIVE_FILEWATCHER_SERVICE_CID);
 
 static const Module::CIDEntry kToolkitCIDs[] = {
   { &kNS_TOOLKIT_APPSTARTUP_CID, false, nullptr, nsAppStartupConstructor },
 #if defined(MOZ_HAS_TERMINATOR)
   { &kNS_TOOLKIT_TERMINATOR_CID, false, nullptr, nsTerminatorConstructor },
 #endif
+  { &kNS_TOOLKIT_PERFORMANCESTATSSERVICE_CID, false, nullptr, nsPerformanceStatsServiceConstructor },
   { &kNS_USERINFO_CID, false, nullptr, nsUserInfoConstructor },
   { &kNS_ALERTSSERVICE_CID, false, nullptr, nsAlertsServiceConstructor },
 #if !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
   { &kNS_PARENTALCONTROLSSERVICE_CID, false, nullptr, nsParentalControlsServiceConstructor },
 #endif
   { &kNS_DOWNLOADMANAGER_CID, false, nullptr, nsDownloadManagerConstructor },
   { &kNS_DOWNLOADPLATFORM_CID, false, nullptr, DownloadPlatformConstructor },
   { &kNS_DOWNLOAD_CID, false, nullptr, nsDownloadProxyConstructor },
@@ -177,16 +183,17 @@ static const Module::CIDEntry kToolkitCI
   { nullptr }
 };
 
 static const Module::ContractIDEntry kToolkitContracts[] = {
   { NS_APPSTARTUP_CONTRACTID, &kNS_TOOLKIT_APPSTARTUP_CID },
 #if defined(MOZ_HAS_TERMINATOR)
   { NS_TOOLKIT_TERMINATOR_CONTRACTID, &kNS_TOOLKIT_TERMINATOR_CID },
 #endif
+  { NS_TOOLKIT_PERFORMANCESTATSSERVICE_CONTRACTID, &kNS_TOOLKIT_PERFORMANCESTATSSERVICE_CID },
   { NS_USERINFO_CONTRACTID, &kNS_USERINFO_CID },
   { NS_ALERTSERVICE_CONTRACTID, &kNS_ALERTSSERVICE_CID },
 #if !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
   { NS_PARENTALCONTROLSSERVICE_CONTRACTID, &kNS_PARENTALCONTROLSSERVICE_CID },
 #endif
   { NS_DOWNLOADMANAGER_CONTRACTID, &kNS_DOWNLOADMANAGER_CID },
   { NS_DOWNLOADPLATFORM_CONTRACTID, &kNS_DOWNLOADPLATFORM_CID },
   { NS_FIND_SERVICE_CONTRACTID, &kNS_FIND_SERVICE_CID },
--- a/toolkit/components/moz.build
+++ b/toolkit/components/moz.build
@@ -5,18 +5,18 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # These component dirs are built for all apps (including suite)
 if CONFIG['MOZ_ENABLE_XREMOTE']:
     DIRS += ['remote']
 
 DIRS += [
     'aboutcache',
-    'aboutcompartments',
     'aboutmemory',
+    'aboutperformance',
     'addoncompat',
     'alerts',
     'apppicker',
     'asyncshutdown',
     'commandlines',
     'console',
     'contentprefs',
     'cookie',
--- a/toolkit/modules/AddonWatcher.jsm
+++ b/toolkit/modules/AddonWatcher.jsm
@@ -8,79 +8,167 @@
 this.EXPORTED_SYMBOLS = ["AddonWatcher"];
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                   "resource://gre/modules/Preferences.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "console",
+                                  "resource://gre/modules/devtools/Console.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "performanceStatsService",
+                                  "@mozilla.org/toolkit/performance-stats-service;1");
 
 let AddonWatcher = {
-  _lastAddonTime: {},
+  _previousPerformanceIndicators: {},
   _timer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
   _callback: null,
-  _interval: 1500,
+
+  /**
+   * The interval at which we poll the available performance information
+   * to find out about possibly slow add-ons, in milliseconds.
+   */
+  _interval: 15000,
   _ignoreList: null,
+  /**
+   * Initialize and launch the AddonWatcher.
+   *
+   * @param {function} callback A callback, called whenever we determine
+   * that an add-on is causing performance issues. It takes as argument
+   *  {string} addonId The identifier of the add-on known to cause issues.
+   *  {string} reason The reason for which the add-on has been flagged,
+   *     as one of "totalCPOWTime" (the add-on has caused blocking process
+   *     communications, which freeze the UX)
+   *     or "missedFrames" (the add-on has caused user-visible missed frames).
+   *  {number} badness An indicator of the gravity of the performance issues.
+   *     For `totalCPOWTime`, this is the number of µs spent performing
+   *     blocking process communications during the latest slice.
+   *     For `missedFrames`, this is the number of times the add-on has caused
+   *     user-visible missed frames during the latest slice.
+   */
   init: function(callback) {
     if (!callback) {
       return;
     }
 
     if (this._callback) {
+      // Already initialized
       return;
     }
 
     this._interval = Preferences.get("browser.addon-watch.interval", 15000);
     if (this._interval == -1) {
+      // Deactivated by preferences
       return;
     }
 
     this._callback = callback;
     try {
       this._ignoreList = new Set(JSON.parse(Preferences.get("browser.addon-watch.ignore", null)));
     } catch (ex) {
       // probably some malformed JSON, ignore and carry on
       this._ignoreList = new Set();
     }
-    this._timer.initWithCallback(this._checkAddons.bind(this), this._interval, Ci.nsITimer.TYPE_REPEATING_SLACK);
+
+    // Start monitoring
+    this.paused = false;
   },
   uninit: function() {
     if (this._timer) {
       this._timer.cancel();
       this._timer = null;
     }
+    performanceStatsService.isStopwatchActive = false;
   },
+
+  /**
+   * Interrupt temporarily add-on watching.
+   */
+  set paused(isPaused) {
+    if (!this._timer || !this._callback || this._interval == -1) {
+      return;
+    }
+    if (isPaused) {
+      performanceStatsService.isStopwatchActive = false;
+      this._timer.cancel();
+    } else {
+      performanceStatsService.isStopwatchActive = true;
+      this._timer.initWithCallback(this._checkAddons.bind(this), this._interval, Ci.nsITimer.TYPE_REPEATING_SLACK);
+    }
+    this._isPaused = isPaused;
+  },
+  get paused() {
+    return this._isPaused;
+  },
+  _isPaused: true,
+
+  /**
+   * Check the performance of add-ons during the latest slice of time.
+   *
+   * We consider that an add-on is causing slowdown if it has executed
+   * without interruption for at least 64ms (4 frames) at least once
+   * during the latest slice, or if it has used CPOW during the latest
+   * slice.
+   */
   _checkAddons: function() {
-    let compartmentInfo = Cc["@mozilla.org/compartment-info;1"]
-      .getService(Ci.nsICompartmentInfo);
-    let compartments = compartmentInfo.getCompartments();
-    let count = compartments.length;
-    let addons = {};
-    for (let i = 0; i < count; i++) {
-      let compartment = compartments.queryElementAt(i, Ci.nsICompartment);
-      if (compartment.addonId) {
-        if (addons[compartment.addonId]) {
-          addons[compartment.addonId] += compartment.time;
-        } else {
-          addons[compartment.addonId] = compartment.time;
+    try {
+      let snapshot = performanceStatsService.snapshot;
+      let componentsStats = snapshot.getComponentsData();
+      let update = {};
+      for (let item of componentsStats) {
+        let addonId = item.addonId;
+        if (!item.isSystem || !addonId) {
+          // We are only interested in add-ons.
+          continue;
+        }
+        if (this._ignoreList.has(addonId)) {
+          // This add-on has been explicitly put in the ignore list
+          // by the user. Don't waste time with it.
+          continue;
+        }
+        let current = {
+          missedFrames: item.getMissedFrames(),
+          totalCPOWTime: item.totalCPOWTime,
+        };
+        let previous = this._previousPerformanceIndicators[addonId];
+        update[addonId] = current;
+
+        if (!previous) {
+          // This is the first time we see the addon, so we are probably
+          // executed right during/after startup. Performance is always
+          // weird during startup, with the JIT warming up, competition
+          // in disk access, etc. so we do not take this as a reason to
+          // display the slow addon warning.
+          continue;
+        }
+
+        // Now determine if anything bad has happened during the latest slice.
+        let reason = null;
+        let gravity = 0;
+        if (previous.missedFrames[2] < current.missedFrames[2]) {
+          // We have caused at least 4 frames to be missed, which is user-visible
+          // (or almost user-visible).
+          reason = "missedFrames";
+          gravity = current.missedFrames - previous.missedFrames;
+        } else if (previous.totalCPOWTime < current.totalCPOWTime) {
+          // We have caused blocking inter-process communication.
+          reason = "totalCPOWTime";
+          gravity = current.totalCPOWTime - previous.totalCPOWTime;
+        }
+
+        try {
+          console.log("Detected slow add-on", addonId, reason, gravity);
+          this._callback(addonId, reason, gravity);
+        } catch (ex) {
+          console.error("Error while processing slow add-on callback", ex, addonId, reason, gravity);
         }
       }
-    }
-    let limit = this._interval * Preferences.get("browser.addon-watch.percentage-limit", 75) * 10;
-    for (let addonId in addons) {
-      if (!this._ignoreList.has(addonId)) {
-        if (!this._lastAddonTime[addonId]) {
-          this._lastAddonTime[addonId] = 0;
-        }
-        if ((addons[addonId] - this._lastAddonTime[addonId]) > limit) {
-          this._callback(addonId);
-        }
-        this._lastAddonTime[addonId] = addons[addonId];
-      }
+    } catch (ex) {
+      console.error("Error in AddonWatcher._checkAddons", ex);
     }
   },
   ignoreAddonForSession: function(addonid) {
     this._ignoreList.add(addonid);
   },
   ignoreAddonPermanently: function(addonid) {
     this._ignoreList.add(addonid);
     try {
--- a/xpcom/build/XPCOMInit.cpp
+++ b/xpcom/build/XPCOMInit.cpp
@@ -45,18 +45,16 @@
 
 #include "nsComponentManager.h"
 #include "nsCategoryManagerUtils.h"
 #include "nsIServiceManager.h"
 
 #include "nsThreadManager.h"
 #include "nsThreadPool.h"
 
-#include "nsCompartmentInfo.h"
-
 #include "xptinfo.h"
 #include "nsIInterfaceInfoManager.h"
 #include "xptiprivate.h"
 #include "mozilla/XPTInterfaceInfoManager.h"
 
 #include "nsTimerImpl.h"
 #include "TimerThread.h"
 
@@ -237,18 +235,16 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacUtil
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSystemInfo, Init)
 
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMemoryReporterManager, Init)
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsMemoryInfoDumper)
 
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsStatusReporterManager, Init)
 
-NS_GENERIC_FACTORY_CONSTRUCTOR(nsCompartmentInfo)
-
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsIOUtil)
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSecurityConsoleMessage)
 
 static nsresult
 nsThreadManagerGetSingleton(nsISupports* aOuter,
                             const nsIID& aIID,
                             void** aInstancePtr)
--- a/xpcom/build/XPCOMModule.inc
+++ b/xpcom/build/XPCOMModule.inc
@@ -77,9 +77,8 @@
 
     COMPONENT(SYSTEMINFO, nsSystemInfoConstructor)
     COMPONENT(MEMORY_REPORTER_MANAGER, nsMemoryReporterManagerConstructor)
     COMPONENT(MEMORY_INFO_DUMPER, nsMemoryInfoDumperConstructor)
     COMPONENT(IOUTIL, nsIOUtilConstructor)
     COMPONENT(CYCLE_COLLECTOR_LOGGER, nsCycleCollectorLoggerConstructor)
     COMPONENT(MESSAGE_LOOP, nsMessageLoopConstructor)
     COMPONENT(STATUS_REPORTER_MANAGER, nsStatusReporterManagerConstructor)
-    COMPONENT(COMPARTMENT_INFO, nsCompartmentInfoConstructor)
\ No newline at end of file