# Copyright 2013 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. import logging import sys from telemetry.value import histogram from telemetry.value import histogram_util from telemetry.value import scalar from metrics import Metric _HISTOGRAMS = [ { 'name': 'V8.MemoryExternalFragmentationTotal', 'units': 'percent', 'display_name': 'V8_MemoryExternalFragmentationTotal', 'type': histogram_util.RENDERER_HISTOGRAM, 'description': 'Total external memory fragmentation after each GC in ' 'percent.', }, { 'name': 'V8.MemoryHeapSampleTotalCommitted', 'units': 'kb', 'display_name': 'V8_MemoryHeapSampleTotalCommitted', 'type': histogram_util.RENDERER_HISTOGRAM, 'description': 'The total size of committed memory used by V8 after ' 'each GC in KB.' }, { 'name': 'V8.MemoryHeapSampleTotalUsed', 'units': 'kb', 'display_name': 'V8_MemoryHeapSampleTotalUsed', 'type': histogram_util.RENDERER_HISTOGRAM, 'description': 'The total size of live memory used by V8 after each ' 'GC in KB.', }, { 'name': 'V8.MemoryHeapSampleMaximumCommitted', 'units': 'kb', 'display_name': 'V8_MemoryHeapSampleMaximumCommitted', 'type': histogram_util.RENDERER_HISTOGRAM }, { 'name': 'Memory.RendererUsed', 'units': 'kb', 'display_name': 'Memory_RendererUsed', 'type': histogram_util.RENDERER_HISTOGRAM }, { 'name': 'Memory.BrowserUsed', 'units': 'kb', 'display_name': 'Memory_BrowserUsed', 'type': histogram_util.BROWSER_HISTOGRAM }, ] class MemoryMetric(Metric): """MemoryMetric gathers memory statistics from the browser object. This includes both per-page histogram stats, most about javascript memory usage, and overall memory stats from the system for the whole test run.""" def __init__(self, browser): super(MemoryMetric, self).__init__() self._browser = browser start_memory_stats = self._browser.memory_stats self._start_commit_charge = None if 'SystemCommitCharge' in start_memory_stats: self._start_commit_charge = start_memory_stats['SystemCommitCharge'] self._memory_stats = None self._histogram_start = dict() self._histogram_delta = dict() self._started = False @classmethod def CustomizeBrowserOptions(cls, options): options.AppendExtraBrowserArgs([ '--enable-stats-collection-bindings', # For a hard-coded set of Google pages (such as GMail), we produce # custom memory histograms (V8.Something_gmail) instead of the generic # histograms (V8.Something), if we detect that a renderer is only # rendering this page and no other pages. For this test, we need to # disable histogram customizing, so that we get the same generic # histograms produced for all pages. '--disable-histogram-customizer' ]) def Start(self, page, tab): """Start the per-page preparation for this metric. Here, this consists of recording the start value of all the histograms. """ if not self._browser.supports_memory_metrics: logging.warning('Memory metrics not supported.') return self._started = True for h in _HISTOGRAMS: histogram_data = histogram_util.GetHistogram( h['type'], h['name'], tab) # Histogram data may not be available if not histogram_data: continue self._histogram_start[h['name']] = histogram_data def Stop(self, page, tab): """Prepare the results for this page. The results are the differences between the current histogram values and the values when Start() was called. """ if not self._browser.supports_memory_metrics: return assert self._started, 'Must call Start() first' for h in _HISTOGRAMS: # Histogram data may not be available if h['name'] not in self._histogram_start: continue histogram_data = histogram_util.GetHistogram( h['type'], h['name'], tab) self._histogram_delta[h['name']] = histogram_util.SubtractHistogram( histogram_data, self._histogram_start[h['name']]) # Optional argument trace_name is not in base class Metric. # pylint: disable=W0221 def AddResults(self, tab, results, trace_name=None): """Add results for this page to the results object.""" if not self._browser.supports_memory_metrics: return assert self._histogram_delta, 'Must call Stop() first' for h in _HISTOGRAMS: # Histogram data may not be available if h['name'] not in self._histogram_start: continue results.AddValue(histogram.HistogramValue( results.current_page, h['display_name'], h['units'], raw_value_json=self._histogram_delta[h['name']], important=False, description=h.get('description'))) self._memory_stats = self._browser.memory_stats if not self._memory_stats['Browser']: return AddResultsForProcesses(results, self._memory_stats, metric_trace_name=trace_name) if self._start_commit_charge: end_commit_charge = self._memory_stats['SystemCommitCharge'] commit_charge_difference = end_commit_charge - self._start_commit_charge results.AddValue(scalar.ScalarValue( results.current_page, 'commit_charge.' + (trace_name or 'commit_charge'), 'kb', commit_charge_difference, important=False, description='System commit charge (committed memory pages).')) results.AddValue(scalar.ScalarValue( results.current_page, 'processes.' + (trace_name or 'processes'), 'count', self._memory_stats['ProcessCount'], important=False, description='Number of processes used by Chrome.')) def AddResultsForProcesses(results, memory_stats, chart_trace_name='final', metric_trace_name=None, exclude_metrics=None): """Adds memory stats for browser, renderer and gpu processes. Args: results: A telemetry.results.PageTestResults object. memory_stats: System memory stats collected. chart_trace_name: Trace to identify memory metrics. Default is 'final'. metric_trace_name: Trace to identify the metric results per test page. exclude_metrics: List of memory metrics to exclude from results, e.g. VM, WorkingSetSize, etc. """ metric = 'resident_set_size' if sys.platform == 'win32': metric = 'working_set' exclude_metrics = exclude_metrics or {} def AddResultsForProcessTypes(process_types_memory, process_type_trace): """Add all results for a given set of process types. Args: process_types_memory: A list of process types, e.g. Browser, 'Renderer'. process_type_trace: The name of this set of process types in the output. """ def AddResult(value_name_memory, value_name_trace, description): """Add a result for a given statistic. Args: value_name_memory: Name of some statistic, e.g. VM, WorkingSetSize. value_name_trace: Name of this statistic to be used in the output. """ if value_name_memory in exclude_metrics: return if len(process_types_memory) > 1 and value_name_memory.endswith('Peak'): return values = [] for process_type_memory in process_types_memory: stats = memory_stats[process_type_memory] if value_name_memory in stats: values.append(stats[value_name_memory]) if values: if metric_trace_name: current_trace = '%s_%s' % (metric_trace_name, process_type_trace) chart_name = value_name_trace else: current_trace = '%s_%s' % (value_name_trace, process_type_trace) chart_name = current_trace results.AddValue(scalar.ScalarValue( results.current_page, '%s.%s' % (chart_name, current_trace), 'kb', sum(values) / 1024, important=False, description=description)) AddResult('VM', 'vm_%s_size' % chart_trace_name, 'Virtual Memory Size (address space allocated).') AddResult('WorkingSetSize', 'vm_%s_%s_size' % (metric, chart_trace_name), 'Working Set Size (Windows) or Resident Set Size (other ' 'platforms).') AddResult('PrivateDirty', 'vm_private_dirty_%s' % chart_trace_name, 'Private Dirty is basically the amount of RAM inside the ' 'process that can not be paged to disk (it is not backed by the ' 'same data on disk), and is not shared with any other ' 'processes. Another way to look at this is the RAM that will ' 'become available to the system when that process goes away ' '(and probably quickly subsumed into caches and other uses of ' 'it).') AddResult('ProportionalSetSize', 'vm_proportional_set_size_%s' % chart_trace_name, 'The Proportional Set Size (PSS) number is a metric the kernel ' 'computes that takes into account memory sharing -- basically ' 'each page of RAM in a process is scaled by a ratio of the ' 'number of other processes also using that page. This way you ' 'can (in theory) add up the PSS across all processes to see ' 'the total RAM they are using, and compare PSS between ' 'processes to get a rough idea of their relative weight.') AddResult('SharedDirty', 'vm_shared_dirty_%s' % chart_trace_name, 'Shared Dirty is the amount of RAM outside the process that can ' 'not be paged to disk, and is shared with other processes.') AddResult('VMPeak', 'vm_peak_size', 'The peak Virtual Memory Size (address space allocated) usage ' 'achieved by the * process.') AddResult('WorkingSetSizePeak', '%s_peak_size' % metric, 'Peak Working Set Size.') AddResultsForProcessTypes(['Browser'], 'browser') AddResultsForProcessTypes(['Renderer'], 'renderer') AddResultsForProcessTypes(['Gpu'], 'gpu') AddResultsForProcessTypes(['Browser', 'Renderer', 'Gpu'], 'total')