StartupPerformance.sys.mjs (8767B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 const lazy = {}; 6 ChromeUtils.defineESModuleGetters(lazy, { 7 clearTimeout: "resource://gre/modules/Timer.sys.mjs", 8 setTimeout: "resource://gre/modules/Timer.sys.mjs", 9 }); 10 11 const COLLECT_RESULTS_AFTER_MS = 10000; 12 13 const OBSERVED_TOPICS = [ 14 "sessionstore-restoring-on-startup", 15 "sessionstore-initiating-manual-restore", 16 ]; 17 18 export var StartupPerformance = { 19 /** 20 * Once we have finished restoring initial tabs, we broadcast on this topic. 21 */ 22 RESTORED_TOPIC: "sessionstore-finished-restoring-initial-tabs", 23 24 // Instant at which we have started restoration (notification "sessionstore-restoring-on-startup") 25 _startTimeStamp: null, 26 27 // Latest instant at which we have finished restoring a tab (DOM event "SSTabRestored") 28 _latestRestoredTimeStamp: null, 29 30 // A promise resolved once we have finished restoring all the startup tabs. 31 _promiseFinished: null, 32 33 // Function `resolve()` for `_promiseFinished`. 34 _resolveFinished: null, 35 36 // A timer 37 _deadlineTimer: null, 38 39 // `true` once the timer has fired 40 _hasFired: false, 41 42 // `true` once we are restored 43 _isRestored: false, 44 45 // Statistics on the session we need to restore. 46 _totalNumberOfEagerTabs: 0, 47 _totalNumberOfTabs: 0, 48 _totalNumberOfWindows: 0, 49 50 init() { 51 for (let topic of OBSERVED_TOPICS) { 52 Services.obs.addObserver(this, topic); 53 } 54 }, 55 56 /** 57 * Return the timestamp at which we finished restoring the latest tab. 58 * 59 * This information is not really interesting until we have finished restoring 60 * tabs. 61 */ 62 get latestRestoredTimeStamp() { 63 return this._latestRestoredTimeStamp; 64 }, 65 66 /** 67 * `true` once we have finished restoring startup tabs. 68 */ 69 get isRestored() { 70 return this._isRestored; 71 }, 72 73 // Called when restoration starts. 74 // Record the start timestamp, setup the timer and `this._promiseFinished`. 75 // Behavior is unspecified if there was already an ongoing measure. 76 _onRestorationStarts(isAutoRestore) { 77 ChromeUtils.addProfilerMarker("_onRestorationStarts"); 78 this._latestRestoredTimeStamp = this._startTimeStamp = Date.now(); 79 this._totalNumberOfEagerTabs = 0; 80 this._totalNumberOfTabs = 0; 81 this._totalNumberOfWindows = 0; 82 83 // While we may restore several sessions in a single run of the browser, 84 // that's a very unusual case, and not really worth measuring, so let's 85 // stop listening for further restorations. 86 87 for (let topic of OBSERVED_TOPICS) { 88 Services.obs.removeObserver(this, topic); 89 } 90 91 Services.obs.addObserver(this, "sessionstore-single-window-restored"); 92 this._promiseFinished = new Promise(resolve => { 93 this._resolveFinished = resolve; 94 }); 95 this._promiseFinished.then(() => { 96 try { 97 this._isRestored = true; 98 Services.obs.notifyObservers(null, this.RESTORED_TOPIC); 99 100 if (this._latestRestoredTimeStamp == this._startTimeStamp) { 101 // Apparently, we haven't restored any tab. 102 return; 103 } 104 105 // Once we are done restoring tabs, update Telemetry. 106 let delta = this._latestRestoredTimeStamp - this._startTimeStamp; 107 if (isAutoRestore) { 108 Glean.sessionRestore.autoRestoreDurationUntilEagerTabsRestored.accumulateSingleSample( 109 delta 110 ); 111 } else { 112 Glean.sessionRestore.manualRestoreDurationUntilEagerTabsRestored.accumulateSingleSample( 113 delta 114 ); 115 } 116 Glean.sessionRestore.numberOfEagerTabsRestored.accumulateSingleSample( 117 this._totalNumberOfEagerTabs 118 ); 119 Glean.sessionRestore.numberOfTabsRestored.accumulateSingleSample( 120 this._totalNumberOfTabs 121 ); 122 Glean.sessionRestore.numberOfWindowsRestored.accumulateSingleSample( 123 this._totalNumberOfWindows 124 ); 125 126 // Reset 127 this._startTimeStamp = null; 128 } catch (ex) { 129 console.error("StartupPerformance: error after resolving promise", ex); 130 } 131 }); 132 }, 133 134 _startTimer() { 135 if (this._hasFired) { 136 return; 137 } 138 if (this._deadlineTimer) { 139 lazy.clearTimeout(this._deadlineTimer); 140 } 141 this._deadlineTimer = lazy.setTimeout(() => { 142 try { 143 this._resolveFinished(); 144 } catch (ex) { 145 console.error("StartupPerformance: Error in timeout handler", ex); 146 } finally { 147 // Clean up. 148 this._deadlineTimer = null; 149 this._hasFired = true; 150 this._resolveFinished = null; 151 Services.obs.removeObserver( 152 this, 153 "sessionstore-single-window-restored" 154 ); 155 } 156 }, COLLECT_RESULTS_AFTER_MS); 157 }, 158 159 observe(subject, topic) { 160 try { 161 switch (topic) { 162 case "sessionstore-restoring-on-startup": 163 this._onRestorationStarts(true); 164 break; 165 case "sessionstore-initiating-manual-restore": 166 this._onRestorationStarts(false); 167 break; 168 case "sessionstore-single-window-restored": 169 { 170 // Session Restore has just opened a window with (initially empty) tabs. 171 // Some of these tabs will be restored eagerly, while others will be 172 // restored on demand. The process becomes usable only when all windows 173 // have finished restored their eager tabs. 174 // 175 // While it would be possible to track the restoration of each tab 176 // from within SessionRestore to determine exactly when the process 177 // becomes usable, experience shows that this is too invasive. Rather, 178 // we employ the following heuristic: 179 // - we maintain a timer of `COLLECT_RESULTS_AFTER_MS` that we expect 180 // will be triggered only once all tabs have been restored; 181 // - whenever we restore a new window (hence a bunch of eager tabs), 182 // we postpone the timer to ensure that the new eager tabs have 183 // `COLLECT_RESULTS_AFTER_MS` to be restored; 184 // - whenever a tab is restored, we update 185 // `this._latestRestoredTimeStamp`; 186 // - after `COLLECT_RESULTS_AFTER_MS`, we collect the final version 187 // of `this._latestRestoredTimeStamp`, and use it to determine the 188 // entire duration of the collection. 189 // 190 // Note that this heuristic may be inaccurate if a user clicks 191 // immediately on a restore-on-demand tab before the end of 192 // `COLLECT_RESULTS_AFTER_MS`. We assume that this will not 193 // affect too much the results. 194 // 195 // Reset the delay, to give the tabs a little (more) time to restore. 196 this._startTimer(); 197 198 this._totalNumberOfWindows += 1; 199 200 // Observe the restoration of all tabs. We assume that all tabs of this 201 // window will have been restored before `COLLECT_RESULTS_AFTER_MS`. 202 // The last call to `observer` will let us determine how long it took 203 // to reach that point. 204 let win = subject; 205 206 let observer = event => { 207 // We don't care about tab restorations that are due to 208 // a browser flipping from out-of-main-process to in-main-process 209 // or vice-versa. We only care about restorations that are due 210 // to the user switching to a lazily restored tab, or for tabs 211 // that are restoring eagerly. 212 if (!event.detail.isRemotenessUpdate) { 213 ChromeUtils.addProfilerMarker("SSTabRestored"); 214 this._latestRestoredTimeStamp = Date.now(); 215 this._totalNumberOfEagerTabs += 1; 216 } 217 }; 218 win.gBrowser.tabContainer.addEventListener( 219 "SSTabRestored", 220 observer 221 ); 222 this._totalNumberOfTabs += win.gBrowser.tabContainer.itemCount; 223 224 // Once we have finished collecting the results, clean up the observers. 225 this._promiseFinished.then(() => { 226 if (!win.gBrowser.tabContainer) { 227 // May be undefined during shutdown and/or some tests. 228 return; 229 } 230 win.gBrowser.tabContainer.removeEventListener( 231 "SSTabRestored", 232 observer 233 ); 234 }); 235 } 236 break; 237 default: 238 throw new Error(`Unexpected topic ${topic}`); 239 } 240 } catch (ex) { 241 console.error("StartupPerformance error", ex, ex.stack); 242 throw ex; 243 } 244 }, 245 };