Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / tools / telemetry / telemetry / web_perf / timeline_based_measurement.py
index a9cb2df..c399fca 100644 (file)
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-from telemetry.core.backends.chrome import tracing_backend
+import logging
+import os
+from collections import defaultdict
+
+from telemetry.core import util
+from telemetry.core.platform import tracing_category_filter
+from telemetry.core.platform import tracing_options
+from telemetry.page import page_test
+from telemetry.timeline import model as model_module
+from telemetry.value import string as string_value_module
 from telemetry.web_perf import timeline_interaction_record as tir_module
+from telemetry.web_perf.metrics import fast_metric
+from telemetry.web_perf.metrics import responsiveness_metric
 from telemetry.web_perf.metrics import smoothness
-from telemetry.page import page_measurement
-from telemetry.core.timeline import model as model_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'
+V8_OVERHEAD_LEVEL = 'v8-overhead'
 MINIMAL_OVERHEAD_LEVEL = 'minimal-overhead'
 DEBUG_OVERHEAD_LEVEL = 'debug-overhead'
 
 ALL_OVERHEAD_LEVELS = [
   NO_OVERHEAD_LEVEL,
+  V8_OVERHEAD_LEVEL,
   MINIMAL_OVERHEAD_LEVEL,
   DEBUG_OVERHEAD_LEVEL
 ]
 
 
+class InvalidInteractions(Exception):
+  pass
+
+
+def _GetMetricFromMetricType(metric_type):
+  if metric_type == tir_module.IS_FAST:
+    return fast_metric.FastMetric()
+  if metric_type == tir_module.IS_SMOOTH:
+    return smoothness.SmoothnessMetric()
+  if metric_type == tir_module.IS_RESPONSIVE:
+    return responsiveness_metric.ResponsivenessMetric()
+  raise Exception('Unrecognized metric type: %s' % metric_type)
+
+
+# TODO(nednguyen): Get rid of this results wrapper hack after we add interaction
+# record to telemetry value system.
 class _ResultsWrapper(object):
-  def __init__(self, results, interaction_record):
+  def __init__(self, results, label):
     self._results = results
-    self._interaction_record = interaction_record
+    self._result_prefix = label
 
-  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)
+  @property
+  def current_page(self):
+    return self._results.current_page
 
-  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)
+  def _GetResultName(self, trace_name):
+    return '%s-%s' % (self._result_prefix, trace_name)
 
+  def AddValue(self, value):
+    value.name = self._GetResultName(value.name)
+    self._results.AddValue(value)
 
 class _TimelineBasedMetrics(object):
   def __init__(self, model, renderer_thread,
-               create_metrics_for_interaction_record_callback):
+               get_metric_from_metric_type_callback):
     self._model = model
     self._renderer_thread = renderer_thread
-    self._create_metrics_for_interaction_record_callback = \
-        create_metrics_for_interaction_record_callback
+    self._get_metric_from_metric_type_callback = \
+        get_metric_from_metric_type_callback
 
   def FindTimelineInteractionRecords(self):
     # TODO(nduca): Add support for page-load interaction record.
-    return [tir_module.TimelineInteractionRecord.FromEvent(event) for
+    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):
+    all_interactions = self.FindTimelineInteractionRecords()
+    if len(all_interactions) == 0:
+      raise InvalidInteractions('Expected at least one interaction record on '
+                                'the page')
+
+    interactions_by_label = defaultdict(list)
+    for i in all_interactions:
+      interactions_by_label[i.label].append(i)
+
+    for label, interactions in interactions_by_label.iteritems():
+      are_repeatable = [i.repeatable for i in interactions]
+      if not all(are_repeatable) and len(interactions) > 1:
+        raise InvalidInteractions('Duplicate unrepeatable interaction records '
+                                  'on the page')
+      wrapped_results = _ResultsWrapper(results, label)
+      self.UpdateResultsByMetric(interactions, wrapped_results)
+
+  def UpdateResultsByMetric(self, interactions, wrapped_results):
+    for metric_type in tir_module.METRICS:
+      # For each metric type, either all or none of the interactions should
+      # have that metric.
+      interactions_with_metric = [i for i in interactions if
+                                  i.HasMetric(metric_type)]
+      if not interactions_with_metric:
+        continue
+      if len(interactions_with_metric) != len(interactions):
+        raise InvalidInteractions('Interaction records with the same logical '
+                                  'name must have the same flags.')
+      metric = self._get_metric_from_metric_type_callback(metric_type)
+      metric.AddResults(self._model, self._renderer_thread,
+                        interactions, wrapped_results)
+
+
+class TimelineBasedMeasurement(page_test.PageTest):
   """Collects multiple metrics pages based on their interaction records.
 
   A timeline measurement shifts the burden of what metrics to collect onto the
@@ -96,43 +143,60 @@ class TimelineBasedMeasurement(page_measurement.PageMeasurement):
   @classmethod
   def AddCommandLineArgs(cls, parser):
     parser.add_option(
-        '--overhead-level', type='choice',
+        '--overhead-level', dest='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', dest='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:
+    if not tab.browser.platform.tracing_controller.IsChromeTracingSupported(
+        tab.browser):
       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 = ''
+      category_filter = tracing_category_filter.CreateNoOverheadFilter()
+    # TODO(ernstm): Remove this overhead level when benchmark relevant v8 events
+    # become available in the 'benchmark' category.
+    elif self.options.overhead_level == V8_OVERHEAD_LEVEL:
+      category_filter = tracing_category_filter.CreateNoOverheadFilter()
+      category_filter.AddIncludedCategory('v8')
+    elif self.options.overhead_level == MINIMAL_OVERHEAD_LEVEL:
+      category_filter = tracing_category_filter.CreateMinimalOverheadFilter()
     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())
-    return res
-
-  def MeasurePage(self, page, tab, results):
+      category_filter = tracing_category_filter.CreateDebugOverheadFilter()
+
+    for delay in page.GetSyntheticDelayCategories():
+      category_filter.AddSyntheticDelay(delay)
+    options = tracing_options.TracingOptions()
+    options.enable_chrome_trace = True
+    tab.browser.platform.tracing_controller.Start(options, category_filter)
+
+  def ValidateAndMeasurePage(self, page, tab, results):
     """ Collect all possible metrics and added them to results. """
-    trace_result = tab.browser.StopTracing()
+    trace_result = tab.browser.platform.tracing_controller.Stop()
+    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)
+    renderer_thread = model.GetRendererThreadFromTabId(tab.id)
     meta_metrics = _TimelineBasedMetrics(
-      model, renderer_thread, self.CreateMetricsForTimelineInteractionRecord)
+        model, renderer_thread, _GetMetricFromMetricType)
     meta_metrics.AddResults(results)
 
   def CleanUpAfterPage(self, page, tab):
-    if tab.browser.is_tracing_running:
-      tab.browser.StopTracing()
+    if tab.browser.platform.tracing_controller.is_tracing_running:
+      tab.browser.platform.tracing_controller.Stop()