1# Copyright 2014 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.
4import sys
5
6from measurements import smooth_gesture_util
7from telemetry.core.platform import tracing_category_filter
8from telemetry.core.platform import tracing_options
9from telemetry.timeline.model import TimelineModel
10from telemetry.page import page_test
11from telemetry.page.actions import action_runner
12from telemetry.value import list_of_scalar_values
13from telemetry.value import scalar
14from telemetry.web_perf import timeline_interaction_record as tir_module
15from telemetry.web_perf.metrics import smoothness
16
17
18RUN_SMOOTH_ACTIONS = 'RunSmoothAllActions'
19
20# Descriptions for results from platform.GetRawDisplayFrameRateMeasurements().
21DESCRIPTIONS = {
22    'avg_surface_fps': 'Average frames per second as measured by the '
23                       'platform\'s SurfaceFlinger.'
24}
25
26
27class MissingDisplayFrameRateError(page_test.MeasurementFailure):
28  def __init__(self, name):
29    super(MissingDisplayFrameRateError, self).__init__(
30      'Missing display frame rate metrics: ' + name)
31
32class SmoothnessController(object):
33  def __init__(self):
34    self._timeline_model = None
35    self._tracing_timeline_data = None
36    self._interaction = None
37
38  def SetUp(self, page, tab):
39    # FIXME: Remove webkit.console when blink.console lands in chromium and
40    # the ref builds are updated. crbug.com/386847
41    custom_categories = ['webkit.console', 'blink.console', 'benchmark']
42    custom_categories += page.GetSyntheticDelayCategories()
43    category_filter = tracing_category_filter.TracingCategoryFilter()
44    for c in custom_categories:
45      category_filter.AddIncludedCategory(c)
46    options = tracing_options.TracingOptions()
47    options.enable_chrome_trace = True
48    tab.browser.platform.tracing_controller.Start(options, category_filter, 60)
49    if tab.browser.platform.IsRawDisplayFrameRateSupported():
50      tab.browser.platform.StartRawDisplayFrameRateMeasurement()
51
52  def Start(self, tab):
53    # Start the smooth marker for all smooth actions.
54    runner = action_runner.ActionRunner(tab)
55    self._interaction = runner.BeginInteraction(
56        RUN_SMOOTH_ACTIONS, is_smooth=True)
57
58  def Stop(self, tab):
59    # End the smooth marker for all smooth actions.
60    self._interaction.End()
61    # Stop tracing for smoothness metric.
62    if tab.browser.platform.IsRawDisplayFrameRateSupported():
63      tab.browser.platform.StopRawDisplayFrameRateMeasurement()
64    self._tracing_timeline_data = tab.browser.platform.tracing_controller.Stop()
65    self._timeline_model = TimelineModel(
66      timeline_data=self._tracing_timeline_data)
67
68  def AddResults(self, tab, results):
69    # Add results of smoothness metric. This computes the smoothness metric for
70    # the time ranges of gestures, if there is at least one, else the the time
71    # ranges from the first action to the last action.
72
73    renderer_thread = self._timeline_model.GetRendererThreadFromTabId(
74        tab.id)
75    run_smooth_actions_record = None
76    smooth_records = []
77    for event in renderer_thread.async_slices:
78      if not tir_module.IsTimelineInteractionRecord(event.name):
79        continue
80      r = tir_module.TimelineInteractionRecord.FromAsyncEvent(event)
81      if r.label == RUN_SMOOTH_ACTIONS:
82        assert run_smooth_actions_record is None, (
83          'SmoothnessController cannot issue more than 1 %s record' %
84          RUN_SMOOTH_ACTIONS)
85        run_smooth_actions_record = r
86      elif r.is_smooth:
87        smooth_records.append(
88          smooth_gesture_util.GetAdjustedInteractionIfContainGesture(
89            self._timeline_model, r))
90
91    # If there is no other smooth records, we make measurements on time range
92    # marked smoothness_controller itself.
93    # TODO(nednguyen): when crbug.com/239179 is marked fixed, makes sure that
94    # page sets are responsible for issueing the markers themselves.
95    if len(smooth_records) == 0:
96      if run_smooth_actions_record is None:
97        sys.stderr.write('Raw tracing data:\n')
98        sys.stderr.write(repr(self._tracing_timeline_data.EventData()))
99        sys.stderr.write('\n')
100        raise Exception('SmoothnessController failed to issue markers for the '
101                        'whole interaction.')
102      else:
103        smooth_records = [run_smooth_actions_record]
104
105    # Create an interaction_record for this legacy measurement. Since we don't
106    # wrap the results that are sent to smoothness metric, the label will
107    # not be used.
108    smoothness_metric = smoothness.SmoothnessMetric()
109    smoothness_metric.AddResults(
110      self._timeline_model, renderer_thread, smooth_records, results)
111    if tab.browser.platform.IsRawDisplayFrameRateSupported():
112      for r in tab.browser.platform.GetRawDisplayFrameRateMeasurements():
113        if r.value is None:
114          raise MissingDisplayFrameRateError(r.name)
115        if isinstance(r.value, list):
116          results.AddValue(list_of_scalar_values.ListOfScalarValues(
117              results.current_page, r.name, r.unit, r.value,
118              description=DESCRIPTIONS.get(r.name)))
119        else:
120          results.AddValue(scalar.ScalarValue(
121              results.current_page, r.name, r.unit, r.value,
122              description=DESCRIPTIONS.get(r.name)))
123
124  def CleanUp(self, tab):
125    if tab.browser.platform.IsRawDisplayFrameRateSupported():
126      tab.browser.platform.StopRawDisplayFrameRateMeasurement()
127    if tab.browser.platform.tracing_controller.is_tracing_running:
128      tab.browser.platform.tracing_controller.Stop()
129