1# Copyright 2015 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 collections
6
7from telemetry.timeline import memory_dump_event
8from telemetry.value import improvement_direction
9from telemetry.value import list_of_scalar_values
10from telemetry.web_perf.metrics import timeline_based_metric
11
12
13DEFAULT_METRICS = memory_dump_event.MMAPS_METRICS.keys()
14
15
16class MemoryTimelineMetric(timeline_based_metric.TimelineBasedMetric):
17  """MemoryTimelineMetric reports summary stats from memory dump events."""
18
19  def AddResults(self, model, renderer_thread, interactions, results):
20    # Note: This method will be called by TimelineBasedMeasurement once for
21    # each thread x interaction_label combination; where |interactions| is
22    # a list of all interactions sharing the same label that occurred in the
23    # given |renderer_thread|.
24
25    def ContainedIn(dump, interaction):
26      return interaction.start < dump.start and dump.end < interaction.end
27
28    def OccursDuringInteractions(dump):
29      return (
30          # Dump must contain the rendrerer process that requested it,
31          renderer_thread.parent.pid in dump.pids and
32          # ... and fall within the span of an interaction record.
33          any(ContainedIn(dump, interaction) for interaction in interactions))
34
35    def ReportResultsForProcess(memory_dumps, process_name):
36      if not memory_dumps:
37        metric_values = dict.fromkeys(DEFAULT_METRICS)
38        num_processes = None
39        none_reason = 'No memory dumps with mmaps found within interactions'
40      else:
41        metric_values = collections.defaultdict(list)
42        num_processes = []
43        for dump in memory_dumps:
44          for metric, value in dump.GetMemoryUsage().iteritems():
45            metric_values[metric].append(value)
46          num_processes.append(dump.CountProcessMemoryDumps())
47        none_reason = None
48      for metric, values in metric_values.iteritems():
49        results.AddValue(list_of_scalar_values.ListOfScalarValues(
50            page=results.current_page,
51            name='memory_%s_%s' % (metric, process_name),
52            units='bytes',
53            tir_label=interactions[0].label,
54            values=values,
55            none_value_reason=none_reason,
56            improvement_direction=improvement_direction.DOWN))
57      results.AddValue(list_of_scalar_values.ListOfScalarValues(
58            page=results.current_page,
59            name='process_count_%s' % process_name,
60            units='count',
61            tir_label=interactions[0].label,
62            values=num_processes,
63            none_value_reason=none_reason,
64            improvement_direction=improvement_direction.DOWN))
65
66    memory_dumps = filter(OccursDuringInteractions,
67                          model.IterGlobalMemoryDumps())
68
69    # Either all dumps should contain memory maps (Android, Linux), or none
70    # of them (Windows, Mac).
71    assert len(set(dump.has_mmaps for dump in memory_dumps)) <= 1
72
73    ReportResultsForProcess(memory_dumps, 'total')
74
75    memory_dumps_by_process_name = collections.defaultdict(list)
76    for memory_dump in memory_dumps:
77      # Split this global memory_dump into individual process dumps, and then
78      # group them by their process names.
79      process_dumps_by_name = collections.defaultdict(list)
80      for process_dump in memory_dump.IterProcessMemoryDumps():
81        process_name = process_dump.process_name.lower().replace(' ', '_')
82        process_dumps_by_name[process_name].append(process_dump)
83
84      # Merge process dumps that have the same process name into a new
85      # global dump. Note: this is slightly abusing GlobalMemoryDump so that
86      # we can say dump.GetMemoryUsage() on the created dump objects to obtain
87      # the memory usage aggregated per type. This should no longer be needed
88      # after moving to TBMv2. See: http://crbug.com/581716
89      for process_name, process_dumps in process_dumps_by_name.iteritems():
90        memory_dumps_by_process_name[process_name].append(
91            memory_dump_event.GlobalMemoryDump(process_dumps))
92
93    for process_name, memory_dumps in memory_dumps_by_process_name.iteritems():
94      ReportResultsForProcess(memory_dumps, process_name)
95