1# Copyright 2014 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
5from metrics import memory
6from metrics import Metric
7from telemetry.value import scalar
8
9
10class SystemMemoryMetric(Metric):
11  """SystemMemoryMetric gathers system memory statistic.
12
13  This metric collects system memory stats per test.  It reports the difference
14  (delta) in system memory starts from the start of the test to the end of it.
15  """
16
17  def __init__(self, browser):
18    super(SystemMemoryMetric, self).__init__()
19    self._browser = browser
20    self._memory_stats_start = None
21    self._memory_stats_end = None
22
23  def Start(self, page, tab):
24    """Start the per-page preparation for this metric.
25
26    Records the system memory stats at this point.
27    """
28    self._memory_stats_start = self._browser.memory_stats
29
30  def Stop(self, page, tab):
31    """Prepare the results for this page.
32
33    The results are the differences between the current system memory stats
34    and the values when Start() was called.
35    """
36    assert self._memory_stats_start, 'Must call Start() first'
37    self._memory_stats_end = self._browser.memory_stats
38
39  # |trace_name| and |exclude_metrics| args are not in base class Metric.
40  # pylint: disable=W0221
41  def AddResults(self, tab, results, trace_name=None, exclude_metrics=None):
42    """Add results for this page to the results object.
43
44    Reports the delta in memory stats between the start stats and the end stats
45    (as *_delta metrics). It reports end memory stats in case no matching start
46    memory stats exists.
47
48    Args:
49      trace_name: Trace name to identify the summary results for current page.
50      exclude_metrics: List of memory metrics to exclude from results,
51                       e.g. VM, VMPeak, etc. See AddResultsForProcesses().
52    """
53    assert self._memory_stats_end, 'Must call Stop() first'
54    memory_stats = _SubtractMemoryStats(self._memory_stats_end,
55                                        self._memory_stats_start)
56    if not memory_stats['Browser']:
57      return
58    exclude_metrics = exclude_metrics or {}
59    memory.AddResultsForProcesses(results, memory_stats,
60        metric_trace_name=trace_name, chart_trace_name='delta',
61        exclude_metrics=exclude_metrics)
62
63    if 'SystemCommitCharge' not in exclude_metrics:
64      results.AddValue(scalar.ScalarValue(
65          results.current_page,
66          'commit_charge_delta.%s' % (trace_name or 'commit_charge'), 'kb',
67          memory_stats['SystemCommitCharge'], important=False))
68
69    if 'ProcessCount' not in exclude_metrics:
70      results.AddValue(scalar.ScalarValue(
71          results.current_page,
72          'processes_delta.%s' % (trace_name or 'processes'), 'count',
73          memory_stats['ProcessCount'], important=False))
74
75
76def _SubtractMemoryStats(end_memory_stats, start_memory_stats):
77  """Computes the difference in memory usage stats.
78
79  Each of the two stats arguments is a dict with the following format:
80      {'Browser': {metric: value, ...},
81       'Renderer': {metric: value, ...},
82       'Gpu': {metric: value, ...},
83       'ProcessCount': value,
84       etc
85      }
86  The metrics can be VM, WorkingSetSize, ProportionalSetSize, etc depending on
87  the platform/test.
88
89  NOTE: The only metrics that are not subtracted from original are the *Peak*
90  memory values.
91
92  Returns:
93    A dict of process type names (Browser, Renderer, etc.) to memory usage
94    metrics between the end collected stats and the start collected stats.
95  """
96  memory_stats = {}
97  end_memory_stats = end_memory_stats or {}
98  start_memory_stats = start_memory_stats or {}
99
100  for process_type in end_memory_stats:
101    memory_stats[process_type] = {}
102    end_process_memory = end_memory_stats[process_type]
103    if not end_process_memory:
104      continue
105
106    # If a process has end stats without start stats then report the end stats.
107    # For example, a GPU process that started just after media playback.
108    if (process_type not in start_memory_stats or
109        not start_memory_stats[process_type]):
110      memory_stats[process_type] = end_process_memory
111      continue
112
113    if not isinstance(end_process_memory, dict):
114      start_value = start_memory_stats[process_type] or 0
115      memory_stats[process_type] = end_process_memory - start_value
116    else:
117      for metric in end_process_memory:
118        end_value = end_process_memory[metric]
119        start_value = start_memory_stats[process_type][metric] or 0
120        if 'Peak' in metric:
121          memory_stats[process_type][metric] = end_value
122        else:
123          memory_stats[process_type][metric] = end_value - start_value
124  return memory_stats
125