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