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