1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5// start.js sends a "start" message to set this. 6window.benchmarkConfiguration = {}; 7 8// The callback (e.g. report writer) is set via AddBenchmarckCallback. 9window.benchmarkCallback; 10 11// Url to load before loading target page. 12var kWaitUrl = "http://wprwprwpr/web-page-replay-generate-200"; 13 14// Constant StatCounter Names 15var kTcpReadBytes = "tcp.read_bytes"; 16var kTcpWriteBytes = "tcp.write_bytes"; 17var kRequestCount = "HttpNetworkTransaction.Count"; 18var kConnectCount = "tcp.connect"; 19 20function CHECK(expr, comment) { 21 if (!expr) { 22 console.log(comment); 23 alert(comment); 24 } 25} 26 27function Result() { 28 var me_ = this; 29 this.url = ""; 30 this.firstPaintTime = 0; 31 this.readBytesKB = 0; 32 this.writeBytesKB = 0; 33 this.numRequests = 0; 34 this.numConnects = 0; 35 this.timing = {}; // window.performance.timing 36 this.getTotalTime = function() { 37 var totalTime = 0 38 if (me_.timing.navigationStart && me_.timing.loadEventEnd) { 39 totalTime = me_.timing.loadEventEnd - me_.timing.navigationStart; 40 } 41 CHECK(totalTime >= 0); 42 return totalTime; 43 } 44} 45 46// Collect all the results for a session (i.e. different pages). 47function ResultsCollection() { 48 var results_ = []; 49 var pages_ = []; 50 var pageResults_ = {}; 51 52 this.addResult = function(result) { 53 results_.push(result); 54 var url = result.url; 55 if (!(url in pageResults_)) { 56 pages_.push(url); 57 pageResults_[url] = []; 58 } 59 pageResults_[url].push(result); 60 } 61 62 this.getPages = function() { 63 return pages_; 64 } 65 66 this.getResults = function() { 67 return results_; 68 } 69 70 this.getTotalTimes = function() { 71 return results_.map(function (t) { return t.getTotalTime(); }); 72 } 73} 74 75// Load a url in the default tab and record the time. 76function PageLoader(url, resultReadyCallback) { 77 var me_ = this; 78 var url_ = url; 79 var resultReadyCallback_ = resultReadyCallback; 80 81 // If it record mode, wait a little longer for lazy loaded resources. 82 var postLoadGraceMs_ = window.isRecordMode ? 5000 : 0; 83 var loadInterval_ = window.loadInterval; 84 var checkInterval_ = window.checkInterval; 85 var timeout_ = window.timeout; 86 var maxLoadChecks_ = window.maxLoadChecks; 87 88 var preloadFunc_; 89 var timeoutId_; 90 var isFinished_; 91 var result_; 92 93 var initialReadBytes_; 94 var initialWriteBytes_; 95 var initialRequestCount_; 96 var initialConnectCount_; 97 98 this.result = function() { return result_; }; 99 100 this.run = function() { 101 timeoutId_ = null; 102 isFinished_ = false; 103 result_ = null; 104 initialReadBytes_ = chrome.benchmarking.counter(kTcpReadBytes); 105 initialWriteBytes_ = chrome.benchmarking.counter(kTcpWriteBytes); 106 initialRequestCount_ = chrome.benchmarking.counter(kRequestCount); 107 initialConnectCount_ = chrome.benchmarking.counter(kConnectCount); 108 109 if (me_.preloadFunc_) { 110 me_.preloadFunc_(me_.load_); 111 } else { 112 me_.load_(); 113 } 114 }; 115 116 this.setClearAll = function() { 117 me_.preloadFunc_ = me_.clearAll_; 118 }; 119 120 this.setClearConnections = function() { 121 me_.preloadFunc_ = me_.clearConnections_; 122 }; 123 124 this.clearAll_ = function(callback) { 125 chrome.tabs.getSelected(null, function(tab) { 126 chrome.tabs.update(tab.id, {"url": kWaitUrl}, function() { 127 chrome.benchmarking.clearHostResolverCache(); 128 chrome.benchmarking.clearPredictorCache(); 129 chrome.benchmarking.closeConnections(); 130 var dataToRemove = { 131 "appcache": true, 132 "cache": true, 133 "cookies": true, 134 "downloads": true, 135 "fileSystems": true, 136 "formData": true, 137 "history": true, 138 "indexedDB": true, 139 "localStorage": true, 140 "passwords": true, 141 "pluginData": true, 142 "webSQL": true 143 }; 144 // Add any items new to the API. 145 for (var prop in chrome.browsingData) { 146 var dataName = prop.replace("remove", ""); 147 if (dataName && dataName != prop) { 148 dataName = dataName.charAt(0).toLowerCase() + 149 dataName.substr(1); 150 if (!dataToRemove.hasOwnProperty(dataName)) { 151 console.log("New browsingData API item: " + dataName); 152 dataToRemove[dataName] = true; 153 } 154 } 155 } 156 chrome.browsingData.remove({}, dataToRemove, callback); 157 }); 158 }); 159 }; 160 161 this.clearConnections_ = function(callback) { 162 chrome.benchmarking.closeConnections(); 163 callback(); 164 }; 165 166 this.load_ = function() { 167 console.log("LOAD started: " + url_); 168 setTimeout(function() { 169 chrome.extension.onRequest.addListener(me_.finishLoad_); 170 timeoutId_ = setTimeout(function() { 171 me_.finishLoad_({"loadTimes": null, "timing": null}); 172 }, timeout_); 173 chrome.tabs.getSelected(null, function(tab) { 174 chrome.tabs.update(tab.id, {"url": url_}); 175 }); 176 }, loadInterval_); 177 }; 178 179 this.finishLoad_ = function(msg) { 180 if (!isFinished_) { 181 isFinished_ = true; 182 clearTimeout(timeoutId_); 183 chrome.extension.onRequest.removeListener(me_.finishLoad_); 184 me_.saveResult_(msg.loadTimes, msg.timing); 185 } 186 }; 187 188 this.saveResult_ = function(loadTimes, timing) { 189 result_ = new Result() 190 result_.url = url_; 191 if (!loadTimes || !timing) { 192 console.log("LOAD INCOMPLETE: " + url_); 193 } else { 194 console.log("LOAD complete: " + url_); 195 result_.timing = timing; 196 var baseTime = timing.navigationStart; 197 CHECK(baseTime); 198 result_.firstPaintTime = Math.max(0, 199 Math.round((1000.0 * loadTimes.firstPaintTime) - baseTime)); 200 } 201 result_.readBytesKB = (chrome.benchmarking.counter(kTcpReadBytes) - 202 initialReadBytes_) / 1024; 203 result_.writeBytesKB = (chrome.benchmarking.counter(kTcpWriteBytes) - 204 initialWriteBytes_) / 1024; 205 result_.numRequests = (chrome.benchmarking.counter(kRequestCount) - 206 initialRequestCount_); 207 result_.numConnects = (chrome.benchmarking.counter(kConnectCount) - 208 initialConnectCount_); 209 setTimeout(function() { resultReadyCallback_(me_); }, postLoadGraceMs_); 210 }; 211} 212 213// Load page sets and prepare performance results. 214function SessionLoader(resultsReadyCallback) { 215 var me_ = this; 216 var resultsReadyCallback_ = resultsReadyCallback; 217 var pageSets_ = benchmarkConfiguration.pageSets; 218 var iterations_ = window.iterations; 219 var retries_ = window.retries; 220 221 var pageLoaders_ = []; 222 var resultsCollection_ = new ResultsCollection(); 223 var loaderIndex_ = 0; 224 var retryIndex_ = 0; 225 var iterationIndex_ = 0; 226 227 this.run = function() { 228 me_.createLoaders_(); 229 me_.loadPage_(); 230 } 231 232 this.getResultsCollection = function() { 233 return resultsCollection_; 234 } 235 236 this.createLoaders_ = function() { 237 // Each url becomes one benchmark. 238 for (var i = 0; i < pageSets_.length; i++) { 239 for (var j = 0; j < pageSets_[i].length; j++) { 240 // Remove extra space at the beginning or end of a url. 241 var url = pageSets_[i][j].trim(); 242 // Alert about and ignore blank page which does not get loaded. 243 if (url == "about:blank") { 244 alert("blank page loaded!"); 245 } else if (!url.match(/https?:\/\//)) { 246 // Alert about url that is not in scheme http:// or https://. 247 alert("Skipping url without http:// or https://: " + url); 248 } else { 249 var loader = new PageLoader(url, me_.handleResult_) 250 if (j == 0) { 251 // Clear all browser data for the first page in a sub list. 252 loader.setClearAll(); 253 } else { 254 // Otherwise, only clear the connections. 255 loader.setClearConnections(); 256 } 257 pageLoaders_.push(loader); 258 } 259 } 260 } 261 } 262 263 this.loadPage_ = function() { 264 console.log("LOAD url " + (loaderIndex_ + 1) + " of " + 265 pageLoaders_.length + 266 ", iteration " + (iterationIndex_ + 1) + " of " + 267 iterations_); 268 pageLoaders_[loaderIndex_].run(); 269 } 270 271 this.handleResult_ = function(loader) { 272 var result = loader.result(); 273 resultsCollection_.addResult(result); 274 var totalTime = result.getTotalTime(); 275 if (!totalTime && retryIndex_ < retries_) { 276 retryIndex_++; 277 console.log("LOAD retry, " + retryIndex_); 278 } else { 279 retryIndex_ = 0; 280 console.log("RESULTS url " + (loaderIndex_ + 1) + " of " + 281 pageLoaders_.length + 282 ", iteration " + (iterationIndex_ + 1) + " of " + 283 iterations_ + ": " + totalTime); 284 loaderIndex_++; 285 if (loaderIndex_ >= pageLoaders_.length) { 286 iterationIndex_++; 287 if (iterationIndex_ < iterations_) { 288 loaderIndex_ = 0; 289 } else { 290 resultsReadyCallback_(me_); 291 return; 292 } 293 } 294 } 295 me_.loadPage_(); 296 } 297} 298 299function AddBenchmarkCallback(callback) { 300 window.benchmarkCallback = callback; 301} 302 303function Run() { 304 window.checkInterval = 500; 305 window.loadInterval = 1000; 306 window.timeout = 20000; // max ms before killing page. 307 window.retries = 0; 308 window.isRecordMode = benchmarkConfiguration.isRecordMode; 309 if (window.isRecordMode) { 310 window.iterations = 1; 311 window.timeout = 40000; 312 window.retries = 2; 313 } else { 314 window.iterations = benchmarkConfiguration["iterations"] || 3; 315 } 316 var sessionLoader = new SessionLoader(benchmarkCallback); 317 console.log("pageSets: " + JSON.stringify(benchmarkConfiguration.pageSets)); 318 sessionLoader.run(); 319} 320 321chrome.runtime.onConnect.addListener(function(port) { 322 port.onMessage.addListener(function(data) { 323 if (data.message == "start") { 324 window.benchmarkConfiguration = data.benchmark; 325 Run() 326 } 327 }); 328}); 329