Home | History | Annotate | Download | only in extension
      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.
      6 window.benchmarkConfiguration = {};
      7 
      8 // The callback (e.g. report writer) is set via AddBenchmarckCallback.
      9 window.benchmarkCallback;
     10 
     11 // Url to load before loading target page.
     12 var kWaitUrl = "http://wprwprwpr/web-page-replay-generate-200";
     13 
     14 // Constant StatCounter Names
     15 var kTcpReadBytes = "tcp.read_bytes";
     16 var kTcpWriteBytes = "tcp.write_bytes";
     17 var kRequestCount = "HttpNetworkTransaction.Count";
     18 var kConnectCount = "tcp.connect";
     19 
     20 function CHECK(expr, comment) {
     21   if (!expr) {
     22     console.log(comment);
     23     alert(comment);
     24   }
     25 }
     26 
     27 function 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).
     47 function 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.
     76 function 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.
    214 function 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 
    299 function AddBenchmarkCallback(callback) {
    300   window.benchmarkCallback = callback;
    301 }
    302 
    303 function 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 
    321 chrome.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