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 sys
6
7from metrics import histogram_util
8from metrics import Metric
9from telemetry.value import histogram
10from telemetry.value import scalar
11
12
13_HISTOGRAMS = [
14    {'name': 'V8.MemoryExternalFragmentationTotal', 'units': 'percent',
15     'display_name': 'V8_MemoryExternalFragmentationTotal',
16     'type': histogram_util.RENDERER_HISTOGRAM},
17    {'name': 'V8.MemoryHeapSampleTotalCommitted', 'units': 'kb',
18     'display_name': 'V8_MemoryHeapSampleTotalCommitted',
19     'type': histogram_util.RENDERER_HISTOGRAM},
20    {'name': 'V8.MemoryHeapSampleTotalUsed', 'units': 'kb',
21     'display_name': 'V8_MemoryHeapSampleTotalUsed',
22     'type': histogram_util.RENDERER_HISTOGRAM},
23    {'name': 'V8.MemoryHeapSampleMaximumCommitted', 'units': 'kb',
24     'display_name': 'V8_MemoryHeapSampleMaximumCommitted',
25     'type': histogram_util.RENDERER_HISTOGRAM},
26    {'name': 'Memory.RendererUsed', 'units': 'kb',
27     'display_name': 'Memory_RendererUsed',
28     'type': histogram_util.RENDERER_HISTOGRAM},
29    {'name': 'Memory.BrowserUsed', 'units': 'kb',
30     'display_name': 'Memory_BrowserUsed',
31     'type': histogram_util.BROWSER_HISTOGRAM}]
32
33class MemoryMetric(Metric):
34  """MemoryMetric gathers memory statistics from the browser object.
35
36  This includes both per-page histogram stats, most about javascript
37  memory usage, and overall memory stats from the system for the whole
38  test run."""
39
40  def __init__(self, browser):
41    super(MemoryMetric, self).__init__()
42    self._browser = browser
43    self._start_commit_charge = self._browser.memory_stats['SystemCommitCharge']
44    self._memory_stats = None
45    self._histogram_start = dict()
46    self._histogram_delta = dict()
47
48  @classmethod
49  def CustomizeBrowserOptions(cls, options):
50    options.AppendExtraBrowserArgs([
51        '--enable-stats-collection-bindings',
52        # For a hard-coded set of Google pages (such as GMail), we produce
53        # custom memory histograms (V8.Something_gmail) instead of the generic
54        # histograms (V8.Something), if we detect that a renderer is only
55        # rendering this page and no other pages. For this test, we need to
56        # disable histogram customizing, so that we get the same generic
57        # histograms produced for all pages.
58        '--disable-histogram-customizer'
59    ])
60
61  def Start(self, page, tab):
62    """Start the per-page preparation for this metric.
63
64    Here, this consists of recording the start value of all the histograms.
65    """
66    for h in _HISTOGRAMS:
67      histogram_data = histogram_util.GetHistogram(
68          h['type'], h['name'], tab)
69      # Histogram data may not be available
70      if not histogram_data:
71        continue
72      self._histogram_start[h['name']] = histogram_data
73
74  def Stop(self, page, tab):
75    """Prepare the results for this page.
76
77    The results are the differences between the current histogram values
78    and the values when Start() was called.
79    """
80    assert self._histogram_start, 'Must call Start() first'
81    for h in _HISTOGRAMS:
82      # Histogram data may not be available
83      if h['name'] not in self._histogram_start:
84        continue
85      histogram_data = histogram_util.GetHistogram(
86          h['type'], h['name'], tab)
87      self._histogram_delta[h['name']] = histogram_util.SubtractHistogram(
88          histogram_data, self._histogram_start[h['name']])
89
90  # Optional argument trace_name is not in base class Metric.
91  # pylint: disable=W0221
92  def AddResults(self, tab, results, trace_name=None):
93    """Add results for this page to the results object."""
94    assert self._histogram_delta, 'Must call Stop() first'
95    for h in _HISTOGRAMS:
96      # Histogram data may not be available
97      if h['name'] not in self._histogram_start:
98        continue
99      results.AddValue(histogram.HistogramValue(
100          results.current_page, h['display_name'], h['units'],
101          raw_value_json=self._histogram_delta[h['name']], important=False))
102    self._memory_stats = self._browser.memory_stats
103    if not self._memory_stats['Browser']:
104      return
105    AddResultsForProcesses(results, self._memory_stats,
106                           metric_trace_name=trace_name)
107
108    end_commit_charge = self._memory_stats['SystemCommitCharge']
109    commit_charge_difference = end_commit_charge - self._start_commit_charge
110    results.AddValue(scalar.ScalarValue(
111        results.current_page,
112        'commit_charge.' + (trace_name or 'commit_charge'),
113        'kb',
114        commit_charge_difference, important=False))
115    results.AddValue(scalar.ScalarValue(
116        results.current_page,
117        'processes.' + (trace_name or 'processes'),
118        'count',
119        self._memory_stats['ProcessCount'],
120        important=False))
121
122
123def AddResultsForProcesses(results, memory_stats, chart_trace_name='final',
124                           metric_trace_name=None,
125                           exclude_metrics=None):
126  """Adds memory stats for browser, renderer and gpu processes.
127
128  Args:
129    results: A PageMeasurement results object.
130    memory_stats: System memory stats collected.
131    chart_trace_name: Trace to identify memory metrics. Default is 'final'.
132    metric_trace_name: Trace to identify the metric results per test page.
133    exclude_metrics: List of memory metrics to exclude from results,
134                     e.g. VM, WorkingSetSize, etc.
135  """
136  metric = 'resident_set_size'
137  if sys.platform == 'win32':
138    metric = 'working_set'
139
140  exclude_metrics = exclude_metrics or {}
141
142  def AddResultsForProcessTypes(process_types_memory, process_type_trace):
143    """Add all results for a given set of process types.
144
145    Args:
146      process_types_memory: A list of process types, e.g. Browser, 'Renderer'
147      process_type_trace: The name of this set of process types in the output
148    """
149    def AddResult(value_name_memory, value_name_trace):
150      """Add a result for a given statistic.
151
152      Args:
153        value_name_memory: Name of some statistic, e.g. VM, WorkingSetSize
154        value_name_trace: Name of this statistic to be used in the output
155      """
156      if value_name_memory in exclude_metrics:
157        return
158      if len(process_types_memory) > 1 and value_name_memory.endswith('Peak'):
159        return
160      values = []
161      for process_type_memory in process_types_memory:
162        stats = memory_stats[process_type_memory]
163        if value_name_memory in stats:
164          values.append(stats[value_name_memory])
165      if values:
166        if metric_trace_name:
167          current_trace = '%s_%s' % (metric_trace_name, process_type_trace)
168          chart_name = value_name_trace
169        else:
170          current_trace = '%s_%s' % (value_name_trace, process_type_trace)
171          chart_name = current_trace
172        results.AddValue(scalar.ScalarValue(
173            results.current_page, '%s.%s' % (chart_name, current_trace), 'kb',
174            sum(values) / 1024, important=False))
175
176    AddResult('VM', 'vm_%s_size' % chart_trace_name)
177    AddResult('WorkingSetSize', 'vm_%s_%s_size' % (metric, chart_trace_name))
178    AddResult('PrivateDirty', 'vm_private_dirty_%s' % chart_trace_name)
179    AddResult('ProportionalSetSize',
180              'vm_proportional_set_size_%s' % chart_trace_name)
181    AddResult('SharedDirty', 'vm_shared_dirty_%s' % chart_trace_name)
182    AddResult('VMPeak', 'vm_peak_size')
183    AddResult('WorkingSetSizePeak', '%s_peak_size' % metric)
184
185  AddResultsForProcessTypes(['Browser'], 'browser')
186  AddResultsForProcessTypes(['Renderer'], 'renderer')
187  AddResultsForProcessTypes(['Gpu'], 'gpu')
188  AddResultsForProcessTypes(['Browser', 'Renderer', 'Gpu'], 'total')
189