# Copyright 2014 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 logging
import os

from telemetry.core import util
from telemetry.core.backends.chrome import tracing_backend
from telemetry.core.timeline import model as model_module
from telemetry.web_perf import timeline_interaction_record as tir_module
from telemetry.web_perf.metrics import smoothness
from telemetry.web_perf.metrics import responsiveness_metric
from telemetry.page import page_measurement
from telemetry.value import string as string_value_module


# TimelineBasedMeasurement considers all instrumentation as producing a single
# timeline. But, depending on the amount of instrumentation that is enabled,
# overhead increases. The user of the measurement must therefore chose between
# a few levels of instrumentation.
NO_OVERHEAD_LEVEL = 'no-overhead'
MINIMAL_OVERHEAD_LEVEL = 'minimal-overhead'
DEBUG_OVERHEAD_LEVEL = 'debug-overhead'

ALL_OVERHEAD_LEVELS = [
  NO_OVERHEAD_LEVEL,
  MINIMAL_OVERHEAD_LEVEL,
  DEBUG_OVERHEAD_LEVEL
]


class _ResultsWrapper(object):
  def __init__(self, results, interaction_record):
    self._results = results
    self._interaction_record = interaction_record

  def Add(self, trace_name, units, value, chart_name=None, data_type='default'):
    trace_name = self._interaction_record.GetResultNameFor(trace_name)
    self._results.Add(trace_name, units, value, chart_name, data_type)

  def AddSummary(self, trace_name, units, value, chart_name=None,
                  data_type='default'):
    trace_name = self._interaction_record.GetResultNameFor(trace_name)
    self._results.AddSummary(trace_name, units, value, chart_name, data_type)


class _TimelineBasedMetrics(object):
  def __init__(self, model, renderer_thread,
               create_metrics_for_interaction_record_callback):
    self._model = model
    self._renderer_thread = renderer_thread
    self._create_metrics_for_interaction_record_callback = \
        create_metrics_for_interaction_record_callback

  def FindTimelineInteractionRecords(self):
    # TODO(nduca): Add support for page-load interaction record.
    return [tir_module.TimelineInteractionRecord.FromAsyncEvent(event) for
            event in self._renderer_thread.async_slices
            if tir_module.IsTimelineInteractionRecord(event.name)]

  def AddResults(self, results):
    interactions = self.FindTimelineInteractionRecords()
    if len(interactions) == 0:
      raise Exception('Expected at least one Interaction on the page')
    for interaction in interactions:
      metrics = \
          self._create_metrics_for_interaction_record_callback(interaction)
      wrapped_results = _ResultsWrapper(results, interaction)
      for m in metrics:
        m.AddResults(self._model, self._renderer_thread,
                     [interaction], wrapped_results)


class TimelineBasedMeasurement(page_measurement.PageMeasurement):
  """Collects multiple metrics pages based on their interaction records.

  A timeline measurement shifts the burden of what metrics to collect onto the
  page under test, or the pageset running that page. Instead of the measurement
  having a fixed set of values it collects about the page, the page being tested
  issues (via javascript) an Interaction record into the user timing API that
  describing what the page is doing at that time, as well as a standardized set
  of flags describing the semantics of the work being done. The
  TimelineBasedMeasurement object collects a trace that includes both these
  interaction recorsd, and a user-chosen amount of performance data using
  Telemetry's various timeline-producing APIs, tracing especially.

  It then passes the recorded timeline to different TimelineBasedMetrics based
  on those flags. This allows a single run through a page to produce load timing
  data, smoothness data, critical jank information and overall cpu usage
  information.

  For information on how to mark up a page to work with
  TimelineBasedMeasurement, refer to the
  perf.metrics.timeline_interaction_record module.

  """
  def __init__(self):
    super(TimelineBasedMeasurement, self).__init__('RunSmoothness')

  @classmethod
  def AddCommandLineArgs(cls, parser):
    parser.add_option(
        '--overhead-level', type='choice',
        choices=ALL_OVERHEAD_LEVELS,
        default=NO_OVERHEAD_LEVEL,
        help='How much overhead to incur during the measurement.')
    parser.add_option(
        '--trace-dir', type='string', default=None,
        help=('Where to save the trace after the run. If this flag '
              'is not set, the trace will not be saved.'))

  def WillNavigateToPage(self, page, tab):
    if not tab.browser.supports_tracing:
      raise Exception('Not supported')
    assert self.options.overhead_level in ALL_OVERHEAD_LEVELS
    if self.options.overhead_level == NO_OVERHEAD_LEVEL:
      categories = tracing_backend.MINIMAL_TRACE_CATEGORIES
    elif self.options.overhead_level == \
        MINIMAL_OVERHEAD_LEVEL:
      categories = ''
    else:
      categories = '*,disabled-by-default-cc.debug'
    categories = ','.join([categories] + page.GetSyntheticDelayCategories())
    tab.browser.StartTracing(categories)

  def CreateMetricsForTimelineInteractionRecord(self, interaction):
    """ Subclass of TimelineBasedMeasurement overrides this method to customize
    the binding of interaction's flags to metrics.
    """
    res = []
    if interaction.is_smooth:
      res.append(smoothness.SmoothnessMetric())
    if interaction.is_responsive:
      res.append(responsiveness_metric.ResponsivenessMetric())
    return res

  def MeasurePage(self, page, tab, results):
    """ Collect all possible metrics and added them to results. """
    trace_result = tab.browser.StopTracing()
    trace_dir = self.options.trace_dir
    if trace_dir:
      trace_file_path = util.GetSequentialFileName(
          os.path.join(trace_dir, 'trace')) + '.json'
      try:
        with open(trace_file_path, 'w') as f:
          trace_result.Serialize(f)
        results.AddValue(string_value_module.StringValue(
            page, 'trace_path', 'string', trace_file_path))
      except IOError, e:
        logging.error('Cannot open %s. %s' % (trace_file_path, e))

    model = model_module.TimelineModel(trace_result)
    renderer_thread = model.GetRendererThreadFromTab(tab)
    meta_metrics = _TimelineBasedMetrics(
      model, renderer_thread, self.CreateMetricsForTimelineInteractionRecord)
    meta_metrics.AddResults(results)

  def CleanUpAfterPage(self, page, tab):
    if tab.browser.is_tracing_running:
      tab.browser.StopTracing()

@@ -4327,16 +4327,39 @@
 -level',
+ dest='overhead_level',
  type='c
@@ -4550,16 +4550,34 @@
 ce-dir',
+ dest='trace_dir',
  type='s

Add 'dest' to the options of timeline_based_measurement.