# 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):
    del renderer_thread  # unused
    def ContainedIn(dump, interaction):
      return interaction.start < dump.start and dump.end < interaction.end

    def OccursDuringInteractions(dump):
      return 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))
      if process_name == 'total':
        counter_label = 'process_count'
      else:
        # Yields: browser_count, renderer_count, gpu_process_count, etc.
        counter_label = '%s_count' % process_name
      results.AddValue(list_of_scalar_values.ListOfScalarValues(
            page=results.current_page,
            name=counter_label,
            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)
