Bug 1151750 - about:performance now recapitulates alerts. r=mossop
☠☠ backed out by 01682bfcf1e8 ☠ ☠
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Thu, 09 Apr 2015 23:20:35 +0200
changeset 256492 1786e8cb842e68668b8d92e86a6233d541b99108
parent 256491 c426c7eb3b43943b332585ee1ed556e04e4af5fb
child 256493 f6e1a21392c2bd597749f86ead4951e0ca35085d
push id1483
push usermichael.l.comella@gmail.com
push dateFri, 10 Apr 2015 15:12:05 +0000
reviewersmossop
bugs1151750
milestone40.0a1
Bug 1151750 - about:performance now recapitulates alerts. r=mossop
toolkit/components/aboutperformance/content/aboutPerformance.js
toolkit/components/aboutperformance/content/aboutPerformance.xhtml
toolkit/components/aboutperformance/tests/browser/browser_aboutperformance.js
toolkit/modules/AddonWatcher.jsm
--- a/toolkit/components/aboutperformance/content/aboutPerformance.js
+++ b/toolkit/components/aboutperformance/content/aboutPerformance.js
@@ -4,16 +4,17 @@
  * 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
+const { AddonWatcher } = Cu.import("resource://gre/modules/AddonWatcher.jsm", {});
 const { PerformanceStats } = Cu.import("resource://gre/modules/PerformanceStats.jsm", {});
 
 /**
  * The various measures we display.
  */
 const MEASURES = [
   {key: "longestDuration", percentOfDeltaT: false, label: "Jank level"},
   {key: "totalUserTime", percentOfDeltaT: true, label: "User (%)"},
@@ -79,18 +80,103 @@ let State = {
     State._processData = snapshot.processData;
     State._date = now;
     return result;
   }
 };
 
 
 function update() {
+  updateLiveData();
+  updateSlowAddons();
+}
+
+/**
+ * Update the list of slow addons
+ */
+function updateSlowAddons() {
   try {
-    let dataElt = document.getElementById("data");
+    let data = AddonWatcher.alerts;
+    if (data.size == 0) {
+      // Nothing to display.
+      return;
+    }
+    let alerts = 0;
+    for (let [addonId, details] of data) {
+      for (let k of Object.keys(details.alerts)) {
+        alerts += details.alerts[k];
+      }
+    }
+
+    if (!alerts) {
+      // Still nothing to display.
+      return;
+    }
+
+
+    let elData = document.getElementById("slowAddonsList");
+    elData.innerHTML = "";
+    let elTable = document.createElement("table");
+    elData.appendChild(elTable);
+
+    // Generate header
+    let elHeader = document.createElement("tr");
+    elTable.appendChild(elHeader);
+    for (let name of [
+      "Alerts",
+      "Jank level alerts",
+      "(highest jank)",
+      "Cross-Process alerts",
+      "(highest CPOW)"
+    ]) {
+      let elName = document.createElement("td");
+      elName.textContent = name;
+      elHeader.appendChild(elName);
+      elName.classList.add("header");
+    }
+    for (let [addonId, details] of data) {
+      let elAddon = document.createElement("tr");
+
+      // Display the number of occurrences of each alerts
+      let elTotal = document.createElement("td");
+      let total = 0;
+      for (let k of Object.keys(details.alerts)) {
+        total += details.alerts[k];
+      }
+      elTotal.textContent = total;
+      elAddon.appendChild(elTotal);
+
+      for (let filter of ["longestDuration", "totalCPOWTime"]) {
+        for (let stat of ["alerts", "peaks"]) {
+          let el = document.createElement("td");
+          el.textContent = details[stat][filter] || 0;
+          elAddon.appendChild(el);
+        }
+      }
+
+      // Display the name of the add-on
+      let elName = document.createElement("td");
+      elAddon.appendChild(elName);
+      AddonManager.getAddonByID(addonId, a => {
+        elName.textContent = a ? a.name : addonId
+      });
+
+      elTable.appendChild(elAddon);
+    }
+  } catch (ex) {
+    console.error(ex);
+  }
+}
+
+/**
+ * Update the table of live data.
+ */
+function updateLiveData() {
+  try {
+    let dataElt = document.getElementById("liveData");
     dataElt.innerHTML = "";
 
     // Generate table headers
     let headerElt = document.createElement("tr");
     dataElt.appendChild(headerElt);
     headerElt.classList.add("header");
     for (let column of [...MEASURES, {key: "name", name: ""}]) {
       let el = document.createElement("td");
@@ -132,17 +218,17 @@ function update() {
       let id = item.id;
       el.classList.add("contents");
       el.classList.add("name");
       row.appendChild(el);
       if (item.addonId) {
         let _el = el;
         let _item = item;
         AddonManager.getAddonByID(item.addonId, a => {
-          _el.textContent = a?a.name:_item.name
+          _el.textContent = a ? a.name : _item.name
         });
       } else {
         el.textContent = item.name;
       }
     }
   } catch (ex) {
     console.error(ex);
   }
--- a/toolkit/components/aboutperformance/content/aboutPerformance.xhtml
+++ b/toolkit/components/aboutperformance/content/aboutPerformance.xhtml
@@ -69,12 +69,20 @@
         color: rgb(225, 0, 0);
       }
       td.jank9 {
         color: rgb(255, 0, 0);
       }
     </style>
   </head>
   <body onload="go()">
-    <table id="data">
+
+    <h1>Performance monitor</h1>
+    <table id="liveData">
     </table>
+
+    <h1>Slow add-ons alerts</h1>
+    <div id="slowAddonsList">
+      (none)
+    </div>
+
   </body>
 </html>
--- a/toolkit/components/aboutperformance/tests/browser/browser_aboutperformance.js
+++ b/toolkit/components/aboutperformance/tests/browser/browser_aboutperformance.js
@@ -11,17 +11,17 @@ const URL = "http://example.com/browser/
 function frameScript() {
   "use strict";
 
   addMessageListener("aboutperformance-test:hasItems", ({data: url}) => {
     let hasPlatform = false;
     let hasURL = false;
 
     try {
-      let eltData = content.document.getElementById("data");
+      let eltData = content.document.getElementById("liveData");
       if (!eltData) {
         return;
       }
 
       // Find if we have a row for "platform"
       hasPlatform = eltData.querySelector("tr.platform") != null;
 
       // Find if we have a row for our URL
--- a/toolkit/modules/AddonWatcher.jsm
+++ b/toolkit/modules/AddonWatcher.jsm
@@ -18,18 +18,26 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "PerformanceStats",
                                   "resource://gre/modules/PerformanceStats.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
                                   "@mozilla.org/base/telemetry;1",
                                   Ci.nsITelemetry);
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
+const FILTERS = ["longestDuration", "totalCPOWTime"];
+
 let AddonWatcher = {
   _previousPerformanceIndicators: {},
+
+  /**
+   * Stats, designed to be consumed by clients of AddonWatcher.
+   *
+   */
+  _stats: new Map(),
   _timer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
   _callback: null,
   /**
    * The interval at which we poll the available performance information
    * to find out about possibly slow add-ons, in milliseconds.
    */
   _interval: 15000,
   _ignoreList: null,
@@ -156,23 +164,41 @@ let AddonWatcher = {
           Telemetry.getKeyedHistogramById("MISBEHAVING_ADDONS_JANK_LEVEL").
             add(addonId, diff.longestDuration);
         }
         if (diff.totalCPOWTime > 0) {
           Telemetry.getKeyedHistogramById("MISBEHAVING_ADDONS_CPOW_TIME_MS").
             add(addonId, diff.totalCPOWTime / 1000);
         }
 
-        // Report mibehaviors to the user.
+        // Store misbehaviors for about:performance and other clients
+
+        let stats = this._stats.get(addonId);
+        if (!stats) {
+          stats = {
+            peaks: {},
+            alerts: {},
+          };
+          this._stats.set(addonId, stats);
+        }
+
+        // Report misbehaviors to the user.
+
         let reason = null;
 
-        for (let k of ["longestDuration", "totalCPOWTime"]) {
-          if (limits[k] > 0 && diff[k] > limits[k]) {
-            reason = k;
+        for (let filter of FILTERS) {
+          let peak = stats.peaks[filter] || 0;
+          stats.peaks[filter] = Math.max(diff[filter], peak);
+
+          if (limits[filter] <= 0 || diff[filter] <= limits[filter]) {
+            continue;
           }
+
+          reason = filter;
+          stats.alerts[filter] = (stats.alerts[filter] || 0) + 1;
         }
 
         if (!reason) {
           continue;
         }
 
         try {
           this._callback(addonId, reason);
@@ -195,10 +221,29 @@ let AddonWatcher = {
       let ignoreList = JSON.parse(Preferences.get("browser.addon-watch.ignore", "[]"))
       if (!ignoreList.includes(addonid)) {
         ignoreList.push(addonid);
         Preferences.set("browser.addon-watch.ignore", JSON.stringify(ignoreList));
       }
     } catch (ex) {
       Preferences.set("browser.addon-watch.ignore", JSON.stringify([addonid]));
     }
-  }
+  },
+  /**
+   * The list of alerts for this session.
+   *
+   * @type {Map<String, Object>} A map associating addonId to
+   *  objects with fields
+   *  - {Object} peaks The highest values encountered for each filter.
+   *    - {number} longestDuration
+   *    - {number} totalCPOWTime
+   *  - {Object} alerts The number of alerts for each filter.
+   *    - {number} longestDuration
+   *    - {number} totalCPOWTime
+   */
+  get alerts() {
+    let result = new Map();
+    for (let [k, v] of this._stats) {
+      result.set(k, Cu.cloneInto(v, this));
+    }
+    return result;
+  },
 };