summaryrefslogtreecommitdiffstats
path: root/tools/perf/perf_tools/page_cycler.py
blob: d10adcdf7dc6ba24b7dfd65461323cdd36d5ade7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""The page cycler measurement.

This measurement registers a window load handler in which is forces a layout and
then records the value of performance.now(). This call to now() measures the
time from navigationStart (immediately after the previous page's beforeunload
event) until after the layout in the page's load event. In addition, two garbage
collections are performed in between the page loads (in the beforeunload event).
This extra garbage collection time is not included in the measurement times.

Finally, various memory and IO statistics are gathered at the very end of
cycling all pages.
"""

import os
import sys

from perf_tools import histogram_metric
from telemetry.core import util
from telemetry.page import page_measurement

MEMORY_HISTOGRAMS = [
    {'name': 'V8.MemoryExternalFragmentationTotal', 'units': 'percent'},
    {'name': 'V8.MemoryHeapSampleTotalCommitted', 'units': 'kb'},
    {'name': 'V8.MemoryHeapSampleTotalUsed', 'units': 'kb'}]

class PageCycler(page_measurement.PageMeasurement):
  def AddCommandLineOptions(self, parser):
    # The page cyclers should default to 10 iterations. In order to change the
    # default of an option, we must remove and re-add it.
    pageset_repeat_option = parser.get_option('--pageset-repeat')
    pageset_repeat_option.default = 10
    parser.remove_option('--pageset-repeat')
    parser.add_option(pageset_repeat_option)

  def WillRunPageSet(self, tab, results):
    # Avoid paying for a cross-renderer navigation on the first page on legacy
    # page cyclers which use the filesystem.
    if tab.browser.http_server:
      tab.Navigate(tab.browser.http_server.UrlOf('nonexistent.html'))

    with open(os.path.join(os.path.dirname(__file__),
                           'page_cycler.js'), 'r') as f:
      self.page_cycler_js = f.read()  # pylint: disable=W0201

    # pylint: disable=W0201
    self.start_commit_charge = tab.browser.memory_stats['SystemCommitCharge']

    # pylint: disable=W0201
    self.histograms = [histogram_metric.HistogramMetric(
                           h, histogram_metric.RENDERER_HISTOGRAM)
                       for h in MEMORY_HISTOGRAMS]

  def WillNavigateToPage(self, page, tab):
    page.script_to_evaluate_on_commit = self.page_cycler_js

  def DidNavigateToPage(self, page, tab):
    for h in self.histograms:
      h.Start(page, tab)

  def CustomizeBrowserOptions(self, options):
    options.AppendExtraBrowserArg('--enable-stats-collection-bindings')
    options.AppendExtraBrowserArg('--js-flags=--expose_gc')
    options.AppendExtraBrowserArg('--no-sandbox')

    # Old commandline flags used for reference builds.
    options.AppendExtraBrowserArg('--dom-automation')

    # Temporarily disable typical_25 page set on mac.
    if sys.platform == 'darwin' and sys.argv[-1].endswith('/typical_25.json'):
      print 'typical_25 is currently disabled on mac. Skipping test.'
      sys.exit(0)

  def MeasureMemory(self, tab, results):
    memory = tab.browser.memory_stats
    if not memory['Browser']:
      return

    metric = 'resident_set_size'
    if sys.platform == 'win32':
      metric = 'working_set'

    def AddSummariesForProcessTypes(process_types_memory, process_type_trace):
      def AddSummary(value_name_memory, value_name_trace):
        if len(process_types_memory) > 1 and value_name_memory.endswith('Peak'):
          return
        values = []
        for process_type_memory in process_types_memory:
          if value_name_memory in memory[process_type_memory]:
            values.append(memory[process_type_memory][value_name_memory])
        if values:
          results.AddSummary(value_name_trace + process_type_trace,
                             'bytes', sum(values), data_type='unimportant')
      AddSummary('VM', 'vm_final_size_')
      AddSummary('WorkingSetSize', 'vm_%s_final_size_' % metric)
      AddSummary('PrivateDirty', 'vm_private_dirty_final_')
      AddSummary('ProportionalSetSize', 'vm_proportional_set_size_final_')
      AddSummary('VMPeak', 'vm_peak_size_')
      AddSummary('WorkingSetSizePeak', '%s_peak_size_' % metric)

    AddSummariesForProcessTypes(['Browser'], 'browser')
    AddSummariesForProcessTypes(['Renderer'], 'renderer')
    AddSummariesForProcessTypes(['Gpu'], 'gpu')
    AddSummariesForProcessTypes(['Browser', 'Renderer', 'Gpu'], 'total')

    results.AddSummary('commit_charge', 'kb',
                       memory['SystemCommitCharge'] - self.start_commit_charge,
                       data_type='unimportant')
    results.AddSummary('processes', 'count', memory['ProcessCount'],
                       data_type='unimportant')

  def MeasureIO(self, tab, results):
    io_stats = tab.browser.io_stats
    if not io_stats['Browser']:
      return

    def AddSummariesForProcessType(process_type_io, process_type_trace):
      if 'ReadOperationCount' in io_stats[process_type_io]:
        results.AddSummary('read_operations_' + process_type_trace, '',
                           io_stats[process_type_io]
                           ['ReadOperationCount'],
                           data_type='unimportant')
      if 'WriteOperationCount' in io_stats[process_type_io]:
        results.AddSummary('write_operations_' + process_type_trace, '',
                           io_stats[process_type_io]
                           ['WriteOperationCount'],
                           data_type='unimportant')
      if 'ReadTransferCount' in io_stats[process_type_io]:
        results.AddSummary('read_bytes_' + process_type_trace, 'kb',
                           io_stats[process_type_io]
                           ['ReadTransferCount'] / 1024,
                           data_type='unimportant')
      if 'WriteTransferCount' in io_stats[process_type_io]:
        results.AddSummary('write_bytes_' + process_type_trace, 'kb',
                           io_stats[process_type_io]
                           ['WriteTransferCount'] / 1024,
                           data_type='unimportant')
    AddSummariesForProcessType('Browser', 'browser')
    AddSummariesForProcessType('Renderer', 'renderer')
    AddSummariesForProcessType('Gpu', 'gpu')

  def MeasurePage(self, page, tab, results):
    def _IsDone():
      return bool(tab.EvaluateJavaScript('__pc_load_time'))
    util.WaitFor(_IsDone, 60)

    for h in self.histograms:
      h.GetValue(page, tab, results)

    results.Add('page_load_time', 'ms',
                int(float(tab.EvaluateJavaScript('__pc_load_time'))),
                chart_name='times')

  def DidRunPageSet(self, tab, results):
    self.MeasureMemory(tab, results)
    self.MeasureIO(tab, results)