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