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 5 import sys 6 7 from metrics import histogram_util 8 from metrics import Metric 9 from telemetry.value import histogram 10 from 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 33 class 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 123 def 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