1# Copyright 2013 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
5import optparse
6import time
7
8from metrics import v8_object_stats
9from telemetry.page import page_measurement
10from telemetry.value import scalar
11
12# V8 statistics counter names. These can be retrieved using
13# v8_object_stats.V8ObjectStatsMetric.GetV8StatsTable.
14_V8_BYTES_COMMITTED = [
15    'V8.MemoryNewSpaceBytesCommitted',
16    'V8.MemoryOldPointerSpaceBytesCommitted',
17    'V8.MemoryOldDataSpaceBytesCommitted',
18    'V8.MemoryCodeSpaceBytesCommitted',
19    'V8.MemoryMapSpaceBytesCommitted',
20    'V8.MemoryCellSpaceBytesCommitted',
21    'V8.MemoryPropertyCellSpaceBytesCommitted',
22    'V8.MemoryLoSpaceBytesCommitted',
23]
24_V8_BYTES_USED = [
25    'V8.MemoryNewSpaceBytesUsed',
26    'V8.MemoryOldPointerSpaceBytesUsed',
27    'V8.MemoryOldDataSpaceBytesUsed',
28    'V8.MemoryCodeSpaceBytesUsed',
29    'V8.MemoryMapSpaceBytesUsed',
30    'V8.MemoryCellSpaceBytesUsed',
31    'V8.MemoryPropertyCellSpaceBytesUsed',
32    'V8.MemoryLoSpaceBytesUsed',
33]
34_V8_MEMORY_ALLOCATED = [
35    'V8.OsMemoryAllocated',
36]
37
38
39class Endure(page_measurement.PageMeasurement):
40  options = {'skip_navigate_on_repeat': True}
41
42  def __init__(self):
43    super(Endure, self).__init__('RunEndure')
44    # Browser object, saved so that browser.memory_stats can be accessed.
45    self._browser = None
46
47    # Dictionary of trace name to lists of y-values, for making summary values.
48    self._y_values = {}
49
50    # Number of page repetitions since the start of the test.
51    self._iterations_elapsed = 0
52
53    # Start time of the test, used to report total time.
54    self._start_time = None
55
56  @classmethod
57  def AddCommandLineArgs(cls, parser):
58    group = optparse.OptionGroup(parser, 'Endure options')
59    group.add_option('--perf-stats-interval',
60                     dest='perf_stats_interval',
61                     default=1,
62                     type='int',
63                     help='Number of iterations per sampling of statistics.')
64    parser.add_option_group(group)
65
66  def DidStartBrowser(self, browser):
67    """Initializes the measurement after the browser is started."""
68    self._browser = browser
69    self._start_time = time.time()
70
71  def CustomizeBrowserOptions(self, options):
72    """Adds extra command-line options to the browser."""
73    v8_object_stats.V8ObjectStatsMetric.CustomizeBrowserOptions(options)
74
75  def MeasurePage(self, page, tab, results):
76    """Takes a sample and adds a result if enough time has passed."""
77    self._iterations_elapsed += 1
78    if self._iterations_elapsed % int(self.options.perf_stats_interval) == 0:
79      self._SampleStats(tab, results)
80
81  def _SampleStats(self, tab, results):
82    """Records information and add it to the results."""
83
84    def AddPoint(trace_name, units_y, value_y, chart_name=None):
85      """Adds one data point to the results object."""
86      if chart_name:
87        trace_name = '%s.%s' % (chart_name, trace_name)
88      else:
89        assert '.' not in trace_name, (
90            'Trace names cannot contain "." with an empty chart_name since this'
91            ' is used to delimit chart_name.trace_name.')
92      results.AddValue(scalar.ScalarValue(
93          results.current_page, trace_name + '_X', 'iterations',
94          self._iterations_elapsed, important=False))
95      results.AddValue(scalar.ScalarValue(
96          results.current_page, trace_name + '_Y', units_y, value_y,
97          important=False))
98
99      # Save the value so that summary stats can be calculated.
100      if trace_name not in self._y_values:
101        self._y_values[trace_name] = {
102            'units': units_y,
103            'chart_name': chart_name,
104            'values': [],
105        }
106      self._y_values[trace_name]['values'].append(value_y)
107
108    # DOM nodes and event listeners.
109    dom_stats = tab.dom_stats
110    dom_node_count = dom_stats['node_count']
111    event_listener_count = dom_stats['event_listener_count']
112    AddPoint('dom_nodes', 'count', dom_node_count, chart_name='object_counts')
113    AddPoint('event_listeners', 'count', event_listener_count,
114             chart_name='object_counts')
115
116    # Browser and renderer virtual memory stats.
117    memory_stats = self._browser.memory_stats
118    def BrowserVMStats(statistic_name):
119      """Get VM stats from the Browser object in KB."""
120      return memory_stats[statistic_name].get('VM', 0) / 1024.0
121    AddPoint('browser_vm', 'KB', BrowserVMStats('Browser'),
122             chart_name='vm_stats')
123    AddPoint('renderer_vm', 'KB', BrowserVMStats('Renderer'),
124             chart_name='vm_stats')
125    AddPoint('gpu_vm', 'KB', BrowserVMStats('Gpu'), chart_name='vm_stats')
126
127    # V8 counter stats.
128    def V8StatsSum(counters):
129      """Given a list of V8 counter names, get the sum of the values in KB."""
130      stats = v8_object_stats.V8ObjectStatsMetric.GetV8StatsTable(tab, counters)
131      return sum(stats.values()) / 1024.0
132    AddPoint('v8_memory_committed', 'KB', V8StatsSum(_V8_BYTES_COMMITTED),
133             chart_name='v8_counter_stats')
134    AddPoint('v8_memory_used', 'KB', V8StatsSum(_V8_BYTES_USED),
135             chart_name='v8_counter_stats')
136    AddPoint('v8_memory_allocated', 'KB', V8StatsSum(_V8_MEMORY_ALLOCATED),
137             chart_name='v8_counter_stats')
138
139  def DidRunTest(self, browser, results):
140    """Adds summary results (single number for one test run)."""
141    # Report test run length.
142    results.AddSummaryValue(scalar.ScalarValue(None, 'total_iterations',
143                                               'iterations',
144                                               self._iterations_elapsed,
145                                               important=False))
146    results.AddSummaryValue(scalar.ScalarValue(None, 'total_time', 'seconds',
147                                               time.time() - self._start_time,
148                                               important=False))
149
150    # Add summary stats which could be monitored for anomalies.
151    for trace_name in self._y_values:
152      units = self._y_values[trace_name]['units']
153      chart_name = self._y_values[trace_name]['chart_name']
154      values = self._y_values[trace_name]['values']
155      value_name = '%s.%s_max' % (chart_name, trace_name)
156      results.AddSummaryValue(
157          scalar.ScalarValue(None, value_name, units, max(values)))
158