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.
9 from metrics import v8_object_stats
10 from telemetry.page import page_measurement
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',
25 'V8.MemoryNewSpaceBytesUsed',
26 'V8.MemoryOldPointerSpaceBytesUsed',
27 'V8.MemoryOldDataSpaceBytesUsed',
28 'V8.MemoryCodeSpaceBytesUsed',
29 'V8.MemoryMapSpaceBytesUsed',
30 'V8.MemoryCellSpaceBytesUsed',
31 'V8.MemoryPropertyCellSpaceBytesUsed',
32 'V8.MemoryLoSpaceBytesUsed',
34 _V8_MEMORY_ALLOCATED = [
35 'V8.OsMemoryAllocated',
39 class Endure(page_measurement.PageMeasurement):
41 super(Endure, self).__init__('endure')
42 # Browser object, saved so that browser.memory_stats can be accessed.
45 # Dict of trace name to lists of y-values, for making summary values.
48 # Time of test start and last sample, in seconds since the epoch.
49 self._start_time = None
50 self._last_sample_time = 0
52 # Number of page repetitions currently, and at the last sample.
54 self._last_sample_iterations = 0
56 # Interval between stats sampling. One of these attributes will be set when
57 # the perf stats interval option is parsed. The other shall remain as None.
58 self._interval_seconds = None
59 self._interval_iterations = None
61 def AddCommandLineOptions(self, parser):
62 # TODO(tdu): When ProcessCommandLine is added to replace this method,
63 # move the logic in _ParseIntervalOption there to ProcessCommandLine.
64 group = optparse.OptionGroup(parser, 'Endure options')
65 group.add_option('--perf-stats-interval',
66 dest='perf_stats_interval',
69 help='Interval between sampling of statistics, either in '
70 'seconds (specified by appending \'s\') or in number '
72 parser.add_option_group(group)
74 def DidStartBrowser(self, browser):
75 """Saves the Browser object. Called after the browser is started."""
76 self._browser = browser
78 def CustomizeBrowserOptions(self, options):
79 """Adds extra command-line options to the browser."""
80 v8_object_stats.V8ObjectStatsMetric.CustomizeBrowserOptions(options)
82 def CanRunForPage(self, page):
83 """Checks whether a page has the required 'endure' property."""
84 return hasattr(page, 'endure')
86 def WillRunPageRepeats(self, page):
87 """Set-up before starting a new page."""
88 # Reset the starting time for each new page.
89 self._start_time = time.time()
91 # Prefix the page name so it can be picked up by the buildbot script that
92 # parses Endure output.
93 if page.name and not page.display_name.startswith('endure_'):
94 page.name = 'endure_' + page.name
96 def MeasurePage(self, page, tab, results):
97 """Takes a sample and adds a result if enough time has passed."""
98 # Parse the interval option, setting either or seconds or iterations.
99 # This is done here because self.options is not set when any of the above
101 self._ParseIntervalOption()
103 # Check whether the sample interval is specified in seconds or iterations,
104 # and take a sample if it's time.
105 self._iterations += 1
106 if self._interval_seconds:
108 seconds_elapsed = int(round(now - self._last_sample_time))
109 # Note: the time since last sample must be at least as many seconds
110 # as specified; it will usually be more, it will never be less.
111 if seconds_elapsed >= self._interval_seconds:
112 total_seconds = int(round(now - self._start_time))
113 self._SampleStats(tab, results, seconds=total_seconds)
114 self._last_sample_time = now
116 iterations_elapsed = self._iterations - self._last_sample_iterations
117 if iterations_elapsed >= self._interval_iterations:
118 self._SampleStats(tab, results, iterations=self._iterations)
119 self._last_sample_iterations = self._iterations
121 def _ParseIntervalOption(self):
122 """Parses the --perf-stats-interval option that was passed in."""
123 if self._interval_seconds or self._interval_iterations:
125 interval = self.options.perf_stats_interval
126 match = re.match('([0-9]+)([sS]?)$', interval)
127 assert match, ('Invalid value for --perf-stats-interval: %s' % interval)
129 self._interval_seconds = int(match.group(1))
131 self._interval_iterations = int(match.group(1))
132 assert self._interval_seconds or self._interval_iterations
134 def _SampleStats(self, tab, results, seconds=None, iterations=None):
135 """Records information and add it to the results."""
137 def AddPoint(trace_name, units_y, value_y, chart_name=None):
138 """Adds one data point to the results object."""
140 results.Add(trace_name + '_X', 'seconds', seconds,
141 data_type='unimportant', chart_name=chart_name)
143 assert iterations, 'Neither seconds nor iterations given.'
144 results.Add(trace_name + '_X', 'iterations', iterations,
145 data_type='unimportant', chart_name=chart_name)
146 # Add the result as 'unimportant' because we want it to be unmonitored
148 results.Add(trace_name + '_Y', units_y, value_y, data_type='unimportant',
149 chart_name=chart_name)
150 # Save the value in a list so that summary stats can be calculated.
151 if trace_name not in self._y_values:
152 self._y_values[trace_name] = {
154 'chart_name': chart_name,
157 self._y_values[trace_name]['values'].append(value_y)
159 # DOM nodes and event listeners
160 dom_stats = tab.dom_stats
161 dom_node_count = dom_stats['node_count']
162 event_listener_count = dom_stats['event_listener_count']
163 AddPoint('dom_nodes', 'count', dom_node_count, chart_name='object_counts')
164 AddPoint('event_listeners', 'count', event_listener_count,
165 chart_name='object_counts')
167 # Browser and renderer virtual memory stats
168 memory_stats = self._browser.memory_stats
169 def BrowserVMStats(statistic_name):
170 """Get VM stats from the Browser object in KB."""
171 return memory_stats[statistic_name].get('VM', 0) / 1024.0
172 AddPoint('browser_vm', 'KB', BrowserVMStats('Browser'),
173 chart_name='vm_stats')
174 AddPoint('renderer_vm', 'KB', BrowserVMStats('Renderer'),
175 chart_name='vm_stats')
176 AddPoint('gpu_vm', 'KB', BrowserVMStats('Gpu'), chart_name='vm_stats')
179 def V8StatsSum(counters):
180 """Given a list of V8 counter names, get the sum of the values in KB."""
181 stats = v8_object_stats.V8ObjectStatsMetric.GetV8StatsTable(tab, counters)
182 return sum(stats.values()) / 1024.0
183 AddPoint('v8_memory_committed', 'KB', V8StatsSum(_V8_BYTES_COMMITTED),
184 chart_name='v8_counter_stats')
185 AddPoint('v8_memory_used', 'KB', V8StatsSum(_V8_BYTES_USED),
186 chart_name='v8_counter_stats')
187 AddPoint('v8_memory_allocated', 'KB', V8StatsSum(_V8_MEMORY_ALLOCATED),
188 chart_name='v8_counter_stats')
190 def DidRunTest(self, browser, results):
191 """Adds summary results (single number for one test run)."""
192 # Keep track of total test run length in terms of seconds and page
193 # repetitions, so that test run length can be known regardless of
194 # whether it is specified in seconds or iterations.
195 results.AddSummary('total_iterations', 'iterations', self._iterations,
196 data_type='unimportant')
197 results.AddSummary('total_time', 'seconds', time.time() - self._start_time,
198 data_type='unimportant')
200 # Add summary stats which could be monitored for anomalies.
201 for trace_name in self._y_values:
202 units = self._y_values[trace_name]['units']
203 chart_name = self._y_values[trace_name]['chart_name']
204 values = self._y_values[trace_name]['values']
205 results.AddSummary(trace_name + '_max', units, max(values),
206 chart_name=chart_name)