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"""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 os
19import sys
20
21from metrics import histogram
22from metrics import memory
23from telemetry.core import util
24from telemetry.page import page_measurement
25
26
27MEMORY_HISTOGRAMS = [
28    {'name': 'V8.MemoryExternalFragmentationTotal', 'units': 'percent'},
29    {'name': 'V8.MemoryHeapSampleTotalCommitted', 'units': 'kb'},
30    {'name': 'V8.MemoryHeapSampleTotalUsed', 'units': 'kb'}]
31
32
33class PageCycler(page_measurement.PageMeasurement):
34  def __init__(self, *args, **kwargs):
35    super(PageCycler, self).__init__(*args, **kwargs)
36
37    with open(os.path.join(os.path.dirname(__file__),
38                           'page_cycler.js'), 'r') as f:
39      self._page_cycler_js = f.read()
40
41    self._memory_metric = None
42    self._histograms = None
43
44  def AddCommandLineOptions(self, parser):
45    # The page cyclers should default to 10 iterations. In order to change the
46    # default of an option, we must remove and re-add it.
47    # TODO: Remove this after transition to run_benchmark.
48    pageset_repeat_option = parser.get_option('--pageset-repeat')
49    pageset_repeat_option.default = 10
50    parser.remove_option('--pageset-repeat')
51    parser.add_option(pageset_repeat_option)
52
53  def DidStartBrowser(self, browser):
54    """Initialize metrics once right after the browser has been launched."""
55    self._memory_metric = memory.MemoryMetric(browser)
56    self._memory_metric.Start()
57    self._histograms = [histogram.HistogramMetric(
58                           h, histogram.RENDERER_HISTOGRAM)
59                       for h in MEMORY_HISTOGRAMS]
60
61  def DidStartHTTPServer(self, tab):
62    # Avoid paying for a cross-renderer navigation on the first page on legacy
63    # page cyclers which use the filesystem.
64    tab.Navigate(tab.browser.http_server.UrlOf('nonexistent.html'))
65
66  def WillNavigateToPage(self, page, tab):
67    page.script_to_evaluate_on_commit = self._page_cycler_js
68
69  def DidNavigateToPage(self, page, tab):
70    for h in self._histograms:
71      h.Start(page, tab)
72
73  def CustomizeBrowserOptions(self, options):
74    options.AppendExtraBrowserArg('--enable-stats-collection-bindings')
75    options.AppendExtraBrowserArg('--js-flags=--expose_gc')
76    options.AppendExtraBrowserArg('--no-sandbox')
77
78    # Old commandline flags used for reference builds.
79    options.AppendExtraBrowserArg('--dom-automation')
80
81    # Temporarily disable typical_25 page set on mac.
82    if sys.platform == 'darwin' and sys.argv[-1].endswith('/typical_25.json'):
83      print 'typical_25 is currently disabled on mac. Skipping test.'
84      sys.exit(0)
85
86  def MeasureIO(self, tab, results):
87    io_stats = tab.browser.io_stats
88    if not io_stats['Browser']:
89      return
90
91    def AddSummariesForProcessType(process_type_io, process_type_trace):
92      if 'ReadOperationCount' in io_stats[process_type_io]:
93        results.AddSummary('read_operations_' + process_type_trace, '',
94                           io_stats[process_type_io]
95                           ['ReadOperationCount'],
96                           data_type='unimportant')
97      if 'WriteOperationCount' in io_stats[process_type_io]:
98        results.AddSummary('write_operations_' + process_type_trace, '',
99                           io_stats[process_type_io]
100                           ['WriteOperationCount'],
101                           data_type='unimportant')
102      if 'ReadTransferCount' in io_stats[process_type_io]:
103        results.AddSummary('read_bytes_' + process_type_trace, 'kb',
104                           io_stats[process_type_io]
105                           ['ReadTransferCount'] / 1024,
106                           data_type='unimportant')
107      if 'WriteTransferCount' in io_stats[process_type_io]:
108        results.AddSummary('write_bytes_' + process_type_trace, 'kb',
109                           io_stats[process_type_io]
110                           ['WriteTransferCount'] / 1024,
111                           data_type='unimportant')
112    AddSummariesForProcessType('Browser', 'browser')
113    AddSummariesForProcessType('Renderer', 'renderer')
114    AddSummariesForProcessType('Gpu', 'gpu')
115
116  def MeasurePage(self, page, tab, results):
117    def _IsDone():
118      return bool(tab.EvaluateJavaScript('__pc_load_time'))
119    util.WaitFor(_IsDone, 60)
120
121    for h in self._histograms:
122      h.GetValue(page, tab, results)
123
124    results.Add('page_load_time', 'ms',
125                int(float(tab.EvaluateJavaScript('__pc_load_time'))),
126                chart_name='times')
127
128  def DidRunTest(self, tab, results):
129    self._memory_metric.Stop()
130    self._memory_metric.AddResults(tab, results)
131    self.MeasureIO(tab, results)
132
133