1# Copyright 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"""The page cycler measurement.
6
7This measurement registers a window load handler in which is forces a layout and
8then records the value of performance.now(). This call to now() measures the
9time from navigationStart (immediately after the previous page's beforeunload
10event) until after the layout in the page's load event. In addition, two garbage
11collections are performed in between the page loads (in the beforeunload event).
12This extra garbage collection time is not included in the measurement times.
13
14Finally, various memory and IO statistics are gathered at the very end of
15cycling all pages.
16"""
17
18import collections
19import os
20
21from metrics import cpu
22from metrics import iometric
23from metrics import memory
24from metrics import power
25from metrics import speedindex
26from metrics import v8_object_stats
27from telemetry.core import util
28from telemetry.page import page_measurement
29
30class PageCycler(page_measurement.PageMeasurement):
31  options = {'pageset_repeat': 10}
32
33  def __init__(self, *args, **kwargs):
34    super(PageCycler, self).__init__(*args, **kwargs)
35
36    with open(os.path.join(os.path.dirname(__file__),
37                           'page_cycler.js'), 'r') as f:
38      self._page_cycler_js = f.read()
39
40    self._speedindex_metric = speedindex.SpeedIndexMetric()
41    self._memory_metric = None
42    self._power_metric = power.PowerMetric()
43    self._cpu_metric = None
44    self._v8_object_stats_metric = None
45    self._has_loaded_page = collections.defaultdict(int)
46
47  @classmethod
48  def AddCommandLineArgs(cls, parser):
49    parser.add_option('--v8-object-stats',
50        action='store_true',
51        help='Enable detailed V8 object statistics.')
52
53    parser.add_option('--report-speed-index',
54        action='store_true',
55        help='Enable the speed index metric.')
56
57    parser.add_option('--cold-load-percent', type='int', default=50,
58                      help='%d of page visits for which a cold load is forced')
59
60  @classmethod
61  def ProcessCommandLineArgs(cls, parser, args):
62    cls._record_v8_object_stats = args.v8_object_stats
63    cls._report_speed_index = args.report_speed_index
64
65    cold_runs_percent_set = (args.cold_load_percent != None)
66    # Handle requests for cold cache runs
67    if (cold_runs_percent_set and
68        (args.cold_load_percent < 0 or args.cold_load_percent > 100)):
69      raise Exception('--cold-load-percent must be in the range [0-100]')
70
71    # Make sure _cold_run_start_index is an integer multiple of page_repeat.
72    # Without this, --pageset_shuffle + --page_repeat could lead to
73    # assertion failures on _started_warm in WillNavigateToPage.
74    if cold_runs_percent_set:
75      number_warm_pageset_runs = int(
76          (int(args.pageset_repeat) - 1) * (100 - args.cold_load_percent) / 100)
77      number_warm_runs = number_warm_pageset_runs * args.page_repeat
78      cls._cold_run_start_index = number_warm_runs + args.page_repeat
79      cls.discard_first_result = (not args.cold_load_percent or
80                                  cls.discard_first_result)
81    else:
82      cls._cold_run_start_index = args.pageset_repeat * args.page_repeat
83
84  def DidStartBrowser(self, browser):
85    """Initialize metrics once right after the browser has been launched."""
86    self._memory_metric = memory.MemoryMetric(browser)
87    self._cpu_metric = cpu.CpuMetric(browser)
88    if self._record_v8_object_stats:
89      self._v8_object_stats_metric = v8_object_stats.V8ObjectStatsMetric()
90
91  def DidStartHTTPServer(self, tab):
92    # Avoid paying for a cross-renderer navigation on the first page on legacy
93    # page cyclers which use the filesystem.
94    tab.Navigate(tab.browser.http_server.UrlOf('nonexistent.html'))
95
96  def WillNavigateToPage(self, page, tab):
97    page.script_to_evaluate_on_commit = self._page_cycler_js
98    if self.ShouldRunCold(page.url):
99      tab.ClearCache(force=True)
100    if self._report_speed_index:
101      self._speedindex_metric.Start(page, tab)
102    self._cpu_metric.Start(page, tab)
103    self._power_metric.Start(page, tab)
104
105  def DidNavigateToPage(self, page, tab):
106    self._memory_metric.Start(page, tab)
107    if self._record_v8_object_stats:
108      self._v8_object_stats_metric.Start(page, tab)
109
110  def CustomizeBrowserOptions(self, options):
111    memory.MemoryMetric.CustomizeBrowserOptions(options)
112    power.PowerMetric.CustomizeBrowserOptions(options)
113    iometric.IOMetric.CustomizeBrowserOptions(options)
114    options.AppendExtraBrowserArgs('--js-flags=--expose_gc')
115
116    if self._record_v8_object_stats:
117      v8_object_stats.V8ObjectStatsMetric.CustomizeBrowserOptions(options)
118    if self._report_speed_index:
119      self._speedindex_metric.CustomizeBrowserOptions(options)
120
121  def MeasurePage(self, page, tab, results):
122    tab.WaitForJavaScriptExpression('__pc_load_time', 60)
123
124    chart_name_prefix = ('cold_' if self.IsRunCold(page.url) else
125                         'warm_')
126
127    results.Add('page_load_time', 'ms',
128                int(float(tab.EvaluateJavaScript('__pc_load_time'))),
129                chart_name=chart_name_prefix+'times')
130
131    self._has_loaded_page[page.url] += 1
132
133    self._power_metric.Stop(page, tab)
134    self._memory_metric.Stop(page, tab)
135    self._memory_metric.AddResults(tab, results)
136    self._power_metric.AddResults(tab, results)
137
138    self._cpu_metric.Stop(page, tab)
139    self._cpu_metric.AddResults(tab, results)
140    if self._record_v8_object_stats:
141      self._v8_object_stats_metric.Stop(page, tab)
142      self._v8_object_stats_metric.AddResults(tab, results)
143
144    if self._report_speed_index:
145      def SpeedIndexIsFinished():
146        return self._speedindex_metric.IsFinished(tab)
147      util.WaitFor(SpeedIndexIsFinished, 60)
148      self._speedindex_metric.Stop(page, tab)
149      self._speedindex_metric.AddResults(
150          tab, results, chart_name=chart_name_prefix+'speed_index')
151
152  def DidRunTest(self, browser, results):
153    iometric.IOMetric().AddSummaryResults(browser, results)
154
155  def IsRunCold(self, url):
156    return (self.ShouldRunCold(url) or
157            self._has_loaded_page[url] == 0)
158
159  def ShouldRunCold(self, url):
160    # We do the warm runs first for two reasons.  The first is so we can
161    # preserve any initial profile cache for as long as possible.
162    # The second is that, if we did cold runs first, we'd have a transition
163    # page set during which we wanted the run for each URL to both
164    # contribute to the cold data and warm the catch for the following
165    # warm run, and clearing the cache before the load of the following
166    # URL would eliminate the intended warmup for the previous URL.
167    return (self._has_loaded_page[url] >= self._cold_run_start_index)
168
169  def results_are_the_same_on_every_page(self):
170    return False
171