Bug 1245891 - Changing Session Restore Talos tests to include the time to restore actual tabs;r=mconley
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Tue, 15 Mar 2016 11:10:21 +0100
changeset 288771 40100741a9a55b07c9aff4ec936bd30bf20f8a72
parent 288770 d6ce8d232178756c4ba9da4c0d9422facbc5f24d
child 288772 82693561a67f4b4a37caefaa38067c82cbda64e6
push id30089
push userkwierso@gmail.com
push dateWed, 16 Mar 2016 00:26:08 +0000
treeherdermozilla-central@7773387a9a2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs1245891
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1245891 - Changing Session Restore Talos tests to include the time to restore actual tabs;r=mconley MozReview-Commit-ID: 3kjG6ixWNiO MozReview-Commit-ID: ITPYkv6Jj4t
browser/components/sessionstore/StartupPerformance.jsm
testing/talos/talos/startup_test/sessionrestore/addon/SessionRestoreTalosTest.js
testing/talos/talos/startup_test/sessionrestore/addon/install.rdf
testing/talos/talos/startup_test/sessionrestore/addon/sessionrestore-signed.xpi
--- a/browser/components/sessionstore/StartupPerformance.jsm
+++ b/browser/components/sessionstore/StartupPerformance.jsm
@@ -18,19 +18,24 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/Timer.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout",
   "resource://gre/modules/Timer.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
   "resource://gre/modules/Promise.jsm");
 
 const COLLECT_RESULTS_AFTER_MS = 10000;
 
-const TOPICS = ["sessionstore-restoring-on-startup", "sessionstore-initiating-manual-restore"];
+const OBSERVED_TOPICS = ["sessionstore-restoring-on-startup", "sessionstore-initiating-manual-restore"];
 
 this.StartupPerformance = {
+  /**
+   * Once we have finished restoring initial tabs, we broadcast on this topic.
+   */
+  RESTORED_TOPIC: "sessionstore-finished-restoring-initial-tabs",
+
   // Instant at which we have started restoration (notification "sessionstore-restoring-on-startup")
   _startTimeStamp: null,
 
   // Latest instant at which we have finished restoring a tab (DOM event "SSTabRestored")
   _latestRestoredTimeStamp: null,
 
   // A promise resolved once we have finished restoring all the startup tabs.
   _promiseFinished: null,
@@ -39,68 +44,90 @@ this.StartupPerformance = {
   _resolveFinished: null,
 
   // A timer
   _deadlineTimer: null,
 
   // `true` once the timer has fired
   _hasFired: false,
 
+  // `true` once we are restored
+  _isRestored: false,
+
   // Statistics on the session we need to restore.
   _totalNumberOfEagerTabs: 0,
   _totalNumberOfTabs: 0,
   _totalNumberOfWindows: 0,
 
   init: function() {
-    for (let topic of TOPICS) {
+    for (let topic of OBSERVED_TOPICS) {
       Services.obs.addObserver(this, topic, false);
     }
   },
 
+  /**
+   * Return the timestamp at which we finished restoring the latest tab.
+   *
+   * This information is not really interesting until we have finished restoring
+   * tabs.
+   */
+  get latestRestoredTimeStamp() {
+    return this._latestRestoredTimeStamp;
+  },
+
+  /**
+   * `true` once we have finished restoring startup tabs.
+   */
+  get isRestored() {
+    return this._isRestored;
+  },
+
   // Called when restoration starts.
   // Record the start timestamp, setup the timer and `this._promiseFinished`.
   // Behavior is unspecified if there was already an ongoing measure.
   _onRestorationStarts: function(isAutoRestore) {
-    this._startTimeStamp = Date.now();
+    this._latestRestoredTimeStamp = this._startTimeStamp = Date.now();
     this._totalNumberOfEagerTabs = 0;
     this._totalNumberOfTabs = 0;
     this._totalNumberOfWindows = 0;
 
     // While we may restore several sessions in a single run of the browser,
     // that's a very unusual case, and not really worth measuring, so let's
     // stop listening for further restorations.
 
-    for (let topic of TOPICS) {
+    for (let topic of OBSERVED_TOPICS) {
       Services.obs.removeObserver(this, topic);
     }
 
     Services.obs.addObserver(this, "sessionstore-single-window-restored", false);
     this._promiseFinished = new Promise(resolve => {
       this._resolveFinished = resolve;
     });
     this._promiseFinished.then(() => {
       try {
-        if (!this._latestRestoredTimeStamp) {
+        this._isRestored = true;
+        Services.obs.notifyObservers(null, this.RESTORED_TOPIC, "");
+
+        if (this._latestRestoredTimeStamp == this._startTimeStamp) {
           // Apparently, we haven't restored any tab.
           return;
         }
 
         // Once we are done restoring tabs, update Telemetry.
         let histogramName = isAutoRestore ?
           "FX_SESSION_RESTORE_AUTO_RESTORE_DURATION_UNTIL_EAGER_TABS_RESTORED_MS" :
           "FX_SESSION_RESTORE_MANUAL_RESTORE_DURATION_UNTIL_EAGER_TABS_RESTORED_MS";
         let histogram = Services.telemetry.getHistogramById(histogramName);
         let delta = this._latestRestoredTimeStamp - this._startTimeStamp;
         histogram.add(delta);
 
         Services.telemetry.getHistogramById("FX_SESSION_RESTORE_NUMBER_OF_EAGER_TABS_RESTORED").add(this._totalNumberOfEagerTabs);
         Services.telemetry.getHistogramById("FX_SESSION_RESTORE_NUMBER_OF_TABS_RESTORED").add(this._totalNumberOfTabs);
         Services.telemetry.getHistogramById("FX_SESSION_RESTORE_NUMBER_OF_WINDOWS_RESTORED").add(this._totalNumberOfWindows);
 
-
         // Reset
         this._startTimeStamp = null;
      } catch (ex) {
         console.error("StartupPerformance: error after resolving promise", ex);
       }
     });
   },
 
--- a/testing/talos/talos/startup_test/sessionrestore/addon/SessionRestoreTalosTest.js
+++ b/testing/talos/talos/startup_test/sessionrestore/addon/SessionRestoreTalosTest.js
@@ -11,26 +11,27 @@ const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
   "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
   "resource://gre/modules/Timer.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "StartupPerformance",
+  "resource:///modules/sessionstore/StartupPerformance.jsm");
 
 // Observer Service topics.
 const STARTUP_TOPIC = "profile-after-change";
-const RESTORED_TOPIC = "sessionstore-windows-restored";
 
 // Process Message Manager topics.
 const MSG_REQUEST = "session-restore-test?duration";
 const MSG_PROVIDE = "session-restore-test:duration";
 
-function nsSessionRestoreTalosTest() {}
+function nsSessionRestoreTalosTest() { }
 
 nsSessionRestoreTalosTest.prototype = {
   classID: Components.ID("{716346e5-0c45-4aa2-b601-da36f3c74bd8}"),
 
   _xpcom_factory: XPCOMUtils.generateSingletonFactory(nsSessionRestoreTalosTest),
 
   //////////////////////////////////////////////////////////////////////////////
   //// nsISupports
@@ -40,50 +41,75 @@ nsSessionRestoreTalosTest.prototype = {
   //////////////////////////////////////////////////////////////////////////////
   //// nsIObserver
 
   observe: function DS_observe(aSubject, aTopic, aData) {
     switch (aTopic) {
       case STARTUP_TOPIC:
         this.init();
         break;
-      case RESTORED_TOPIC:
-        this.onRestored();
+      case StartupPerformance.RESTORED_TOPIC:
+        this.onReady(true);
         break;
       default:
         throw new Error(`Unknown topic ${aTopic}`);
     }
   },
 
   /**
    * Perform initialization on profile-after-change.
    */
   init: function() {
-    Services.obs.addObserver(this, RESTORED_TOPIC, false);
+    if (StartupPerformance.isRestored) {
+      this.onReady(true);
+    } else {
+      let sessionStartup = Cc["@mozilla.org/browser/sessionstartup;1"]
+                                 .getService(Ci.nsISessionStartup);
+      sessionStartup.onceInitialized.then(() => {
+        if (sessionStartup.sessionType == Ci.nsISessionStartup.NO_SESSION
+        || sessionStartup.sessionType == Ci.nsISessionStartup.DEFER_SESSION) {
+          this.onReady(false);
+        } else {
+          Services.obs.addObserver(this, StartupPerformance.RESTORED_TOPIC, false);
+        }
+      });
+    }
   },
 
   /**
    * Session Restore is complete, hurray.
    */
-  onRestored: function() {
-    setTimeout(function() {
-      // `sessionRestored` actually becomes available only on the next tick.
-      let startup_info = Services.startup.getStartupInfo();
-      let duration = startup_info.sessionRestored - startup_info.sessionRestoreInit;
+  onReady: function(hasRestoredTabs) {
+    if (hasRestoredTabs) {
+      Services.obs.removeObserver(this, StartupPerformance.RESTORED_TOPIC);
+    }
+    try {
+      setTimeout(function() {
+        // `StartupPerformance.latestRestoredTimeStamp` actually becomes available only on the next tick.
+        let startup_info = Services.startup.getStartupInfo();
+        let duration =
+          hasRestoredTabs
+            ? StartupPerformance.latestRestoredTimeStamp - startup_info.sessionRestoreInit
+            : startup_info.sessionRestored - startup_info.sessionRestoreInit;
 
-      // Broadcast startup duration information immediately, in case the talos
-      // page is already loaded.
-      Services.ppmm.broadcastAsyncMessage(MSG_PROVIDE, {duration});
+        // Broadcast startup duration information immediately, in case the talos
+        // page is already loaded.
+        Services.ppmm.broadcastAsyncMessage(MSG_PROVIDE, {duration});
 
-      // Now, in case the talos page isn't loaded yet, prepare to respond if it
-      // requestions the duration information.
-      Services.ppmm.addMessageListener(MSG_REQUEST, function listener() {
-        Services.ppmm.removeMessageListener(MSG_REQUEST, listener);
-        Services.ppmm.broadcastAsyncMessage(MSG_PROVIDE, {duration});
-      });
-    }, 0);
+        // Now, in case the talos page isn't loaded yet, prepare to respond if it
+        // requestions the duration information.
+        Services.ppmm.addMessageListener(MSG_REQUEST, function listener() {
+          Services.ppmm.removeMessageListener(MSG_REQUEST, listener);
+          Services.ppmm.broadcastAsyncMessage(MSG_PROVIDE, {duration});
+        });
+      }, 0);
+    } catch (ex) {
+      dump(`SessionRestoreTalosTest: error ${ex}\n`);
+      dump(ex.stack);
+      dump("\n");
+    }
   },
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Module
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsSessionRestoreTalosTest]);
--- a/testing/talos/talos/startup_test/sessionrestore/addon/install.rdf
+++ b/testing/talos/talos/startup_test/sessionrestore/addon/install.rdf
@@ -1,14 +1,14 @@
 <?xml version="1.0"?><RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"     xmlns:em="http://www.mozilla.org/2004/em-rdf#"><Description about="urn:mozilla:install-manifest">
 
 <!-- Required Items -->
-<em:id>session-restore-test@mozilla.org</em:id>
+<em:id>session-restore-test-2@mozilla.org</em:id>
 <em:name>Session Restore Startup Performance Test</em:name>
-<em:version>1.2.0</em:version>
+<em:version>2.0.9</em:version>
 
 <em:targetApplication>
     <Description>
         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
         <em:minVersion>1.5</em:minVersion>
         <em:maxVersion>*</em:maxVersion>
     </Description>
 </em:targetApplication>
index 3dcd0124a5b03b2ed7aeeedb0cedca2688af7d0e..a4bc441bff09fe2b8c1d3bbc758ef78b5cbfa4ab
GIT binary patch
literal 7586
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
OBLnPSwxuQe!}>3eAch41