blob: 6c6281358852764da7aa42c9618fb9f618a768f3 [file] [log] [blame]
# Copyright 2015 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 collections
from telemetry.timeline import memory_dump_event
from telemetry.value import improvement_direction
from telemetry.value import list_of_scalar_values
from telemetry.web_perf.metrics import timeline_based_metric
DEFAULT_METRICS = memory_dump_event.MMAPS_METRICS.keys()
class MemoryTimelineMetric(timeline_based_metric.TimelineBasedMetric):
"""MemoryTimelineMetric reports summary stats from memory dump events."""
def AddResults(self, model, renderer_thread, interactions, results):
# Note: This method will be called by TimelineBasedMeasurement once for
# each thread x interaction_label combination; where |interactions| is
# a list of all interactions sharing the same label that occurred in the
# given |renderer_thread|.
def ContainedIn(dump, interaction):
return interaction.start < dump.start and dump.end < interaction.end
def OccursDuringInteractions(dump):
return (
# Dump must contain the rendrerer process that requested it,
renderer_thread.parent.pid in dump.pids and
# ... and fall within the span of an interaction record.
any(ContainedIn(dump, interaction) for interaction in interactions))
def ReportResultsForProcess(memory_dumps, process_name):
if not memory_dumps:
metric_values = dict.fromkeys(DEFAULT_METRICS)
num_processes = None
none_reason = 'No memory dumps with mmaps found within interactions'
else:
metric_values = collections.defaultdict(list)
num_processes = []
for dump in memory_dumps:
for metric, value in dump.GetMemoryUsage().iteritems():
metric_values[metric].append(value)
num_processes.append(dump.CountProcessMemoryDumps())
none_reason = None
for metric, values in metric_values.iteritems():
results.AddValue(list_of_scalar_values.ListOfScalarValues(
page=results.current_page,
name='memory_%s_%s' % (metric, process_name),
units='bytes',
tir_label=interactions[0].label,
values=values,
none_value_reason=none_reason,
improvement_direction=improvement_direction.DOWN))
results.AddValue(list_of_scalar_values.ListOfScalarValues(
page=results.current_page,
name='process_count_%s' % process_name,
units='count',
tir_label=interactions[0].label,
values=num_processes,
none_value_reason=none_reason,
improvement_direction=improvement_direction.DOWN))
memory_dumps = filter(OccursDuringInteractions,
model.IterGlobalMemoryDumps())
# Either all dumps should contain memory maps (Android, Linux), or none
# of them (Windows, Mac).
assert len(set(dump.has_mmaps for dump in memory_dumps)) <= 1
ReportResultsForProcess(memory_dumps, 'total')
memory_dumps_by_process_name = collections.defaultdict(list)
for memory_dump in memory_dumps:
# Split this global memory_dump into individual process dumps, and then
# group them by their process names.
process_dumps_by_name = collections.defaultdict(list)
for process_dump in memory_dump.IterProcessMemoryDumps():
process_name = process_dump.process_name.lower().replace(' ', '_')
process_dumps_by_name[process_name].append(process_dump)
# Merge process dumps that have the same process name into a new
# global dump. Note: this is slightly abusing GlobalMemoryDump so that
# we can say dump.GetMemoryUsage() on the created dump objects to obtain
# the memory usage aggregated per type. This should no longer be needed
# after moving to TBMv2. See: http://crbug.com/581716
for process_name, process_dumps in process_dumps_by_name.iteritems():
memory_dumps_by_process_name[process_name].append(
memory_dump_event.GlobalMemoryDump(process_dumps))
for process_name, memory_dumps in memory_dumps_by_process_name.iteritems():
ReportResultsForProcess(memory_dumps, process_name)