forked from mirrors/gecko-dev
		
	 9b79809916
			
		
	
	
		9b79809916
		
	
	
	
	
		
			
			MozReview-Commit-ID: 2pnT2DdKPHV --HG-- extra : rebase_source : f64f2007b7738c259996402a722b3a9bfcab5e0d
		
			
				
	
	
		
			234 lines
		
	
	
	
		
			8.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			234 lines
		
	
	
	
		
			8.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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";
 | |
| 
 | |
| this.EXPORTED_SYMBOLS = ["StartupPerformance"];
 | |
| 
 | |
| const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
 | |
| 
 | |
| Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 | |
| 
 | |
| XPCOMUtils.defineLazyModuleGetter(this, "Services",
 | |
|   "resource://gre/modules/Services.jsm");
 | |
| XPCOMUtils.defineLazyModuleGetter(this, "console",
 | |
|   "resource://gre/modules/Console.jsm");
 | |
| XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
 | |
|   "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 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,
 | |
| 
 | |
|   // Function `resolve()` for `_promiseFinished`.
 | |
|   _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 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._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 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 {
 | |
|         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);
 | |
|       }
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   _startTimer: function() {
 | |
|     if (this._hasFired) {
 | |
|       return;
 | |
|     }
 | |
|     if (this._deadlineTimer) {
 | |
|       clearTimeout(this._deadlineTimer);
 | |
|     }
 | |
|     this._deadlineTimer = setTimeout(() => {
 | |
|       try {
 | |
|         this._resolveFinished();
 | |
|       } catch (ex) {
 | |
|         console.error("StartupPerformance: Error in timeout handler", ex);
 | |
|       } finally {
 | |
|         // Clean up.
 | |
|         this._deadlineTimer = null;
 | |
|         this._hasFired = true;
 | |
|         this._resolveFinished = null;
 | |
|         Services.obs.removeObserver(this, "sessionstore-single-window-restored");
 | |
|       }
 | |
|     }, COLLECT_RESULTS_AFTER_MS);
 | |
|   },
 | |
| 
 | |
|   observe: function(subject, topic, details) {
 | |
|     try {
 | |
|       switch (topic) {
 | |
|         case "sessionstore-restoring-on-startup":
 | |
|           this._onRestorationStarts(true);
 | |
|           break;
 | |
|         case "sessionstore-initiating-manual-restore":
 | |
|           this._onRestorationStarts(false);
 | |
|           break;
 | |
|         case "sessionstore-single-window-restored": {
 | |
|           // Session Restore has just opened a window with (initially empty) tabs.
 | |
|           // Some of these tabs will be restored eagerly, while others will be
 | |
|           // restored on demand. The process becomes usable only when all windows
 | |
|           // have finished restored their eager tabs.
 | |
|           //
 | |
|           // While it would be possible to track the restoration of each tab
 | |
|           // from within SessionRestore to determine exactly when the process
 | |
|           // becomes usable, experience shows that this is too invasive. Rather,
 | |
|           // we employ the following heuristic:
 | |
|           // - we maintain a timer of `COLLECT_RESULTS_AFTER_MS` that we expect
 | |
|           //   will be triggered only once all tabs have been restored;
 | |
|           // - whenever we restore a new window (hence a bunch of eager tabs),
 | |
|           //   we postpone the timer to ensure that the new eager tabs have
 | |
|           //   `COLLECT_RESULTS_AFTER_MS` to be restored;
 | |
|           // - whenever a tab is restored, we update
 | |
|           //   `this._latestRestoredTimeStamp`;
 | |
|           // - after `COLLECT_RESULTS_AFTER_MS`, we collect the final version
 | |
|           //   of `this._latestRestoredTimeStamp`, and use it to determine the
 | |
|           //   entire duration of the collection.
 | |
|           //
 | |
|           // Note that this heuristic may be inaccurate if a user clicks
 | |
|           // immediately on a restore-on-demand tab before the end of
 | |
|           // `COLLECT_RESULTS_AFTER_MS`. We assume that this will not
 | |
|           // affect too much the results.
 | |
|           //
 | |
|           // Reset the delay, to give the tabs a little (more) time to restore.
 | |
|           this._startTimer();
 | |
| 
 | |
|           this._totalNumberOfWindows += 1;
 | |
| 
 | |
|           // Observe the restoration of all tabs. We assume that all tabs of this
 | |
|           // window will have been restored before `COLLECT_RESULTS_AFTER_MS`.
 | |
|           // The last call to `observer` will let us determine how long it took
 | |
|           // to reach that point.
 | |
|           let win = subject;
 | |
| 
 | |
|           let observer = (event) => {
 | |
|             // We don't care about tab restorations that are due to
 | |
|             // a browser flipping from out-of-main-process to in-main-process
 | |
|             // or vice-versa. We only care about restorations that are due
 | |
|             // to the user switching to a lazily restored tab, or for tabs
 | |
|             // that are restoring eagerly.
 | |
|             if (!event.detail.isRemotenessUpdate) {
 | |
|               this._latestRestoredTimeStamp = Date.now();
 | |
|               this._totalNumberOfEagerTabs += 1;
 | |
|             }
 | |
|           };
 | |
|           win.gBrowser.tabContainer.addEventListener("SSTabRestored", observer);
 | |
|           this._totalNumberOfTabs += win.gBrowser.tabContainer.itemCount;
 | |
| 
 | |
|           // Once we have finished collecting the results, clean up the observers.
 | |
|           this._promiseFinished.then(() => {
 | |
|             if (!win.gBrowser.tabContainer) {
 | |
|               // May be undefined during shutdown and/or some tests.
 | |
|               return;
 | |
|             }
 | |
|             win.gBrowser.tabContainer.removeEventListener("SSTabRestored", observer);
 | |
|           });
 | |
|         }
 | |
|         break;
 | |
|         default:
 | |
|           throw new Error(`Unexpected topic ${topic}`);
 | |
|       }
 | |
|     } catch (ex) {
 | |
|       console.error("StartupPerformance error", ex, ex.stack);
 | |
|       throw ex;
 | |
|     }
 | |
|   }
 | |
| };
 |