Home | History | Annotate | Download | only in benchmark
      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 // Round a number to the 1's place.
      6 function formatNumber(str) {
      7   str += '';
      8   if (str == '0') {
      9     return 'N/A ';
     10   }
     11   var x = str.split('.');
     12   var x1 = x[0];
     13   var x2 = x.length > 1 ? '.' + x[1] : '';
     14   var regex = /(\d+)(\d{3})/;
     15   while (regex.test(x1)) {
     16     x1 = x1.replace(regex, '$1' + ',' + '$2');
     17   }
     18   return x1;
     19 }
     20 
     21 // Configuration and results are stored globally.
     22 window.iterations = 10;
     23 window.interval = 200;
     24 window.clearConnections = true;
     25 window.clearCache = true;
     26 window.enableSpdy = false;
     27 window.results = {};
     28 window.results.data = new Array();
     29 window.testUrl = "http://www.google.com/";
     30 window.windowId = 0;
     31 
     32 // Constant StatCounter Names
     33 var kTCPReadBytes = "tcp.read_bytes";
     34 var kTCPWriteBytes = "tcp.write_bytes";
     35 var kRequestCount = "HttpNetworkTransaction.Count";
     36 var kConnectCount = "tcp.connect";
     37 var kSpdySessionCount = "spdy.sessions";
     38 
     39 // The list of currently running benchmarks
     40 var benchmarks = new Array();
     41 var benchmarkIndex = 0;
     42 var benchmarkWindow = 0;
     43 
     44 function addBenchmark(benchmark) {
     45   benchmarks.push(benchmark);
     46   benchmarkIndex = 0;  // Reset the counter when adding benchmarks.
     47 }
     48 
     49 // Array Remove - By John Resig (MIT Licensed)
     50 Array.prototype.remove = function(from, to) {
     51   var rest = this.slice((to || from) + 1 || this.length);
     52   this.length = from < 0 ? this.length + from : from;
     53   return this.push.apply(this, rest);
     54 };
     55 
     56 function removeBenchmark(benchmark) {
     57   var index;
     58   for (var index = 0; index < benchmarks.length; ++index) {
     59     if (benchmarks[index] == benchmark) {
     60       break;
     61     }
     62   }
     63   benchmarks.remove(index);
     64 
     65   // Preserve index ordering when removing from the list.
     66   if (index <= benchmarkIndex) {
     67     benchmarkIndex--;  // Note:  it is okay to drop to -1 here.
     68   }
     69 }
     70 
     71 function benchmarkStillRunning() {
     72   for (var index = 0; index < benchmarks.length; ++index) {
     73     if (benchmarks[index].isRunning()) {
     74       return true;
     75     }
     76   }
     77   return false;
     78 }
     79 
     80 function findBenchmark(url) {
     81   for (var index = 0; index < benchmarks.length; ++index) {
     82     // One common redirection case: if the url ends without a slash and refers
     83     // to a directory, it actually would be redirected to the correct one
     84     // (with a slash). In this case, the url returned by the JS and the one
     85     // stored locally do not match.
     86     if ((benchmarks[index].url() == url) ||
     87         (benchmarks[index].url() + '/' == url)) {
     88       return benchmarks[index];
     89     }
     90   }
     91   return undefined;
     92 }
     93 
     94 function nextBenchmark() {
     95   benchmarkIndex = (benchmarkIndex + 1) % benchmarks.length;
     96   return benchmarks[benchmarkIndex];
     97 }
     98 
     99 function show_options(tabs) {
    100   var tab = tabs[0];
    101   if (window.testUrl == "") {
    102     window.testUrl = tab.url;
    103   }
    104   var tabs = chrome.extension.getViews({"type": "tab"});
    105   if (tabs && tabs.length) {
    106     // To avoid "Uncaught TypeError: Object Window has no method
    107     // 'setUrl' ". Sometimes tabs are not the desired extension tabs.
    108     if (tabs[0].$suburl != undefined) {
    109       tabs[0].setUrl(testUrl);
    110     }
    111     var optionsUrl = chrome.extension.getURL("options.html");
    112     chrome.tabs.getAllInWindow(null, function(all) {
    113       for (var i = 0; i < all.length; i++) {
    114         if (all[i].url == optionsUrl) {
    115           chrome.tabs.update(all[i].id, {selected: true});
    116           return;
    117         }
    118       }
    119     });
    120   } else {
    121     chrome.tabs.create({"url":"options.html"});
    122   }
    123 }
    124 
    125 chrome.browserAction.onClicked.addListener(show_options);
    126 
    127 function Benchmark() {
    128   var runCount_ = 0;
    129   var count_;
    130   var totalTime_;
    131   var me_ = this;
    132   var current_;
    133   var initialRequestCount_;
    134   var initialReadBytes_;
    135   var initialWriteBytes_;
    136 
    137   // Start a test run
    138   this.start = function(url) {
    139     // Check if a run is already in progress.
    140     if (me_.isRunning()) {
    141       return;
    142     }
    143 
    144     console.log("Benchmark testing url: " + url);
    145 
    146     // Add this benchmark to the list of benchmarks running.
    147     addBenchmark(this);
    148 
    149     runCount_ = window.iterations;
    150     count_ = 0;
    151     totalTime_ = 0;
    152 
    153     current_ = {};
    154     current_.url = url;
    155     current_.timestamp = new Date();
    156     current_.viaSpdy = false;
    157     current_.startLoadResults = new Array();  // times to start
    158     current_.commitLoadResults = new Array();  // times to commit
    159     current_.docLoadResults = new Array();  // times to docload
    160     current_.paintResults = new Array();    // times to paint
    161     current_.totalResults = new Array();    // times to complete load
    162     current_.KbytesRead = new Array();
    163     current_.KbytesWritten = new Array();
    164     current_.readbpsResults = new Array();
    165     current_.writebpsResults = new Array();
    166     current_.totalTime = 0;
    167     current_.iterations = 0;
    168     current_.requests = new Array();
    169     current_.connects = new Array();
    170     current_.spdySessions = new Array();
    171     current_.domNum = 0;
    172     current_.maxDepth = 0;
    173     current_.minDepth = 0;
    174     current_.avgDepth = 0;
    175   };
    176 
    177   // Is the benchmark currently in progress.
    178   this.isRunning = function() {
    179     return runCount_ > 0;
    180   };
    181 
    182   // The url which this benchmark is running.
    183   this.url = function() { return current_.url; }
    184 
    185   // Called when the test run completes.
    186   this.finish = function() {
    187     removeBenchmark(this);
    188 
    189     // If we're the last benchmark, close the window.
    190     if (benchmarks.length == 0) {
    191       chrome.tabs.remove(benchmarkWindow.id);
    192       benchmarkWindow = 0;
    193       chrome.tabs.query({active: true, currentWindow: true}, show_options);
    194     }
    195   };
    196 
    197   // Update the UI after a test run.
    198   this.displayResults = function() {
    199     var score = 0;
    200     if (count_ > 0) {
    201       score = totalTime_ / count_;
    202       var text = score.toFixed(1) + "ms avg";
    203       chrome.browserAction.setTitle({"title": text});
    204     }
    205     if (runCount_) {
    206       chrome.browserAction.setBadgeText({"text": "" + runCount_});
    207       chrome.browserAction.setBadgeBackgroundColor({"color": [255, 0, 0, 255]});
    208     } else {
    209       chrome.browserAction.setBadgeText({"text": "" + score.toFixed()});
    210       chrome.browserAction.setBadgeBackgroundColor({"color": [0, 255, 0, 255]});
    211     }
    212 
    213     // Reload the page after each run to show immediate results.
    214     var tabs = chrome.extension.getViews({"type": "tab"});
    215     if (tabs && tabs.length) {
    216       tabs[0].location.reload(true);
    217     }
    218   };
    219 
    220   // Called before starting a page load.
    221   this.pageStart = function() {
    222     initialReadBytes_ = chrome.benchmarking.counter(kTCPReadBytes);
    223     initialWriteBytes_ = chrome.benchmarking.counter(kTCPWriteBytes);
    224     initialRequestCount_ = chrome.benchmarking.counter(kRequestCount);
    225     initialConnectCount_ = chrome.benchmarking.counter(kConnectCount);
    226     initialSpdySessionCount_ = chrome.benchmarking.counter(kSpdySessionCount);
    227   };
    228 
    229   this.openNextPage = function() {
    230     var benchmark = nextBenchmark();
    231     benchmark.pageStart();
    232     chrome.tabs.create({"url": benchmark.url(),"selected": true},
    233                        function(tab) {
    234                          benchmarkWindow = tab;
    235                          // script.js only executes on tested pages
    236                          // not the ones opened by the user.
    237                          chrome.tabs.executeScript(tab.id, {file: "script.js"});
    238                         });
    239   };
    240 
    241   this.prepareToOpenPage = function() {
    242     // After the previous page is closed, this function will apply
    243     // any settings needed to prepare for opening a new page.
    244     // Note: the previous page must be closed, otherwie, the cache
    245     // clearing and connection clearing may not be thorough.
    246 
    247     if (window.clearCache) {
    248       chrome.benchmarking.clearCache();
    249     }
    250 
    251     if (window.clearConnections) {
    252       chrome.benchmarking.closeConnections();
    253     }
    254 
    255     if (window.enableSpdy) {
    256       chrome.benchmarking.enableSpdy(true);
    257     } else {
    258       chrome.benchmarking.enableSpdy(false);
    259     }
    260 
    261     // Go back to the browser so that tasks can run.
    262     setTimeout(me_.openNextPage, window.interval);
    263   };
    264 
    265   this.closePage = function() {
    266     chrome.tabs.remove(benchmarkWindow.id, function() {
    267       me_.prepareToOpenPage();
    268     });
    269   };
    270 
    271   // Run a single page in the benchmark
    272   this.runPage = function() {
    273     if (benchmarkWindow) {
    274       // To avoid the error "Error during tabs.remove: No tab with id xx"
    275       // while debugging, due to user manually closing the benchmark tab.
    276       chrome.tabs.getAllInWindow(null, function(all) {
    277         for (var i = 0; i < all.length; i++) {
    278           if (all[i].id == benchmarkWindow.id) {
    279             me_.closePage();
    280              return;
    281           };
    282         };
    283         me_.prepareToOpenPage();
    284       });
    285     } else {
    286       me_.prepareToOpenPage();
    287     }
    288   };
    289 
    290   // Called when a page finishes loading.
    291   this.pageFinished = function(load_times, domNum, depths) {
    292 
    293      // Make sure the content can be fetched via spdy if it is enabled.
    294     if (window.enableSpdy && !load_times.wasFetchedViaSpdy) {
    295       alert("Can not fetch current url via spdy.\n" +
    296             "Ending current test.");
    297       me_.finish();
    298       // Move on to next benchmarked pages.
    299       if (benchmarks.length > 0) {
    300         if (window.clearConnections) {
    301           chrome.benchmarking.closeConnections();
    302         }
    303         setTimeout(me_.runPage, 100);
    304       }
    305       return;
    306     }
    307 
    308     // If last fetch was via spdy, current fetch should use spdy too. Same
    309     // for vise versa.
    310     if (current_.iterations > 0 &&
    311         current_.viaSpdy != load_times.wasFetchedViaSpdy) {
    312       alert("Error: viaSpdy for current fetch is different from last fetch!\n" +
    313             "Ending current test.");
    314       // Current data set is invalid: remove from the result array.
    315       var currIndex;
    316       currIndex = window.results.data.indexOf(current_, 0);
    317       window.results.data.splice(currIndex, 1);
    318       me_.displayResults();
    319       me_.finish();
    320       if (benchmarks.length > 0) {
    321         if (window.clearConnections) {
    322           chrome.benchmarking.closeConnections();
    323         }
    324         setTimeout(me_.runPage, 100);
    325       }
    326       return;
    327     }
    328 
    329     var requested = load_times.requestTime;
    330     var started = load_times.startLoadTime;
    331     var startLoadTime =
    332         Math.round((load_times.startLoadTime - requested) * 1000.0);
    333     var commitLoadTime =
    334         Math.round((load_times.commitLoadTime - started) * 1000.0);
    335     var docLoadTime =
    336         Math.round((load_times.finishDocumentLoadTime - started) * 1000.0);
    337     var paintTime =
    338         Math.round((load_times.firstPaintTime - started) * 1000.0);
    339     var totalTime =
    340         Math.round((load_times.finishLoadTime - started) * 1000.0);
    341     var firstPaintAfterLoadTime =
    342         Math.round((load_times.firstPaintAfterLoadTime - started) * 1000.0);
    343 
    344     if (paintTime < 0) {
    345       // If the user navigates away from the test while it is running,
    346       // paint may not occur.  Also, some lightweight pages, such as the
    347       // google home page, never trigger a paint measurement via the chrome
    348       // page load timer.
    349       // In this case, the time-to-first paint is effectively the same as the
    350       // time to onLoad().
    351       paintTime = totalTime;
    352     }
    353 
    354     // For our toolbar counters
    355     totalTime_ += totalTime;
    356     count_++;
    357 
    358     // Get the index of current benchmarked page in the result array.
    359     var currIndex;
    360     currIndex = window.results.data.indexOf(current_, 0);
    361 
    362     // Record the result
    363     current_.viaSpdy = load_times.wasFetchedViaSpdy;
    364     current_.iterations++;
    365     current_.startLoadResults.push(startLoadTime);
    366     current_.commitLoadResults.push(commitLoadTime);
    367     current_.docLoadResults.push(docLoadTime);
    368     current_.paintResults.push(paintTime);
    369     current_.totalResults.push(totalTime);
    370     var bytesRead = chrome.benchmarking.counter(kTCPReadBytes) -
    371                                               initialReadBytes_;
    372     var bytesWrite = chrome.benchmarking.counter(kTCPWriteBytes) -
    373                                                initialWriteBytes_;
    374     current_.KbytesRead.push(bytesRead / 1024);
    375     current_.KbytesWritten.push(bytesWrite / 1024);
    376     current_.readbpsResults.push(bytesRead * 8 / totalTime);
    377     current_.writebpsResults.push(bytesWrite * 8 / totalTime);
    378     current_.requests.push(chrome.benchmarking.counter(kRequestCount) -
    379                          initialRequestCount_);
    380     current_.connects.push(chrome.benchmarking.counter(kConnectCount) -
    381                          initialConnectCount_);
    382     current_.spdySessions.push(chrome.benchmarking.counter(kSpdySessionCount) -
    383                          initialSpdySessionCount_);
    384     current_.totalTime += totalTime;
    385     current_.domNum = domNum;
    386     current_.maxDepth = depths[0];
    387     current_.minDepth = depths[1];
    388     current_.avgDepth = depths[2];
    389 
    390     // Insert or update the result data after each run.
    391     if (currIndex == -1) {
    392       window.results.data.push(current_);
    393     } else {
    394       window.results.data[currIndex] = current_;
    395     }
    396 
    397     if (--runCount_ == 0) {
    398       me_.finish();
    399     }
    400 
    401     // If there are more tests, schedule them
    402     if (runCount_ > 0 || benchmarks.length > 0) {
    403       if (window.clearConnections) {
    404         chrome.benchmarking.closeConnections();
    405       }
    406       setTimeout(me_.runPage, 100);
    407     }
    408 
    409     // Update the UI
    410     me_.displayResults();
    411   };
    412 }
    413 
    414 chrome.runtime.onConnect.addListener(function(port) {
    415   port.onMessage.addListener(function(data) {
    416     if (data.message == "load") {
    417       var benchmark = findBenchmark(data.url);
    418       if (benchmark == undefined && benchmarkStillRunning()) {
    419         alert("Error: Loaded url(" + data.url + ") is not the same as what " +
    420               "you set in url box. This could happen if the request is " +
    421               "redirected. Please use the redirected url for testing.");
    422         // Stop the test here.
    423         benchmarks = [];
    424       }
    425       if (benchmark != undefined && benchmark.isRunning()) {
    426         benchmark.pageFinished(data.values, data.domNum, data.domDepths);
    427       }
    428     }
    429   });
    430 });
    431 
    432 function run() {
    433   if (window.clearCache) {
    434     // Show a warning if we will try to clear the cache between runs
    435     // but will also be reusing the same WebKit instance (i.e. Chrome
    436     // is in single-process mode) because the WebKit cache might not get
    437     // completely cleared between runs.
    438     if (chrome.benchmarking.isSingleProcess()) {
    439       alert("Warning: the WebKit cache may not be cleared correctly " +
    440             "between runs because Chrome is running in single-process mode.");
    441     }
    442   }
    443   benchmarks = [];
    444   var urls = testUrl.split(",");
    445   for (var i = 0; i < urls.length; i++) {
    446 
    447     // Remove extra space at the beginning or end of a url.
    448     urls[i] = removeSpace(urls[i]);
    449 
    450     // Alert about and ignore blank page which does not get loaded.
    451     if (urls[i] == "about:blank") {
    452       alert("blank page loaded!");
    453     } else if (!checkScheme(urls[i])) {
    454       // Alert about url that is not in scheme http:// or https://.
    455       alert(urls[i] + " does not start with http:// or https://.");
    456     } else {
    457       var benchmark = new Benchmark();
    458       benchmark.start(urls[i]);  // XXXMB - move to constructor
    459     }
    460   }
    461   benchmarks[0].runPage();
    462 }
    463 
    464 // Remove extra whitespace in the beginning or end of a url string.
    465 function removeSpace(url) {
    466   var tempUrl = url;
    467   while (tempUrl.charAt(tempUrl.length-1) == " ") {
    468     tempUrl = tempUrl.substring(0, tempUrl.length-1);
    469   };
    470   while (tempUrl.charAt(0) == " ") {
    471     tempUrl = tempUrl.substring(1, tempUrl.length);
    472   };
    473   return tempUrl;
    474 }
    475 
    476 // Check whether a Url starts with http:// or https://.
    477 function checkScheme(url) {
    478   var httpStr = "http://";
    479   var httpsStr = "https://";
    480   var urlSubStr1 = url.substring(0, httpStr.length);
    481   var urlSubStr2 = url.substring(0, httpsStr.length);
    482 
    483   if ( (urlSubStr1 == httpStr) || (urlSubStr2 == httpsStr) ) {
    484     return true;
    485   }
    486   return false;
    487 }
    488 
    489 // Run at startup
    490 chrome.windows.getCurrent(function(currentWindow) {
    491   window.windowId = currentWindow.id;
    492 });
    493