Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / tools / telemetry / telemetry / web_perf / metrics / smoothness.py
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.
4
5 from telemetry.perf_tests_helper import FlattenList
6 from telemetry.util import statistics
7 from telemetry.value import list_of_scalar_values
8 from telemetry.value import scalar
9 from telemetry.web_perf.metrics import rendering_stats
10 from telemetry.web_perf.metrics import timeline_based_metric
11
12
13 NOT_ENOUGH_FRAMES_MESSAGE = (
14   'Not enough frames for smoothness metrics (at least two are required).\n'
15   'Issues that have caused this in the past:\n'
16   '- Browser bugs that prevents the page from redrawing\n'
17   '- Bugs in the synthetic gesture code\n'
18   '- Page and benchmark out of sync (e.g. clicked element was renamed)\n'
19   '- Pages that render extremely slow\n'
20   '- Pages that can\'t be scrolled')
21
22
23 class SmoothnessMetric(timeline_based_metric.TimelineBasedMetric):
24   """Computes metrics that measure smoothness of animations over given ranges.
25
26   Animations are typically considered smooth if the frame rates are close to
27   60 frames per second (fps) and uniformly distributed over the sequence. To
28   determine if a timeline range contains a smooth animation, we update the
29   results object with several representative metrics:
30
31     frame_times: A list of raw frame times
32     mean_frame_time: The arithmetic mean of frame times
33     percentage_smooth: Percentage of frames that were hitting 60 FPS.
34     frame_time_discrepancy: The absolute discrepancy of frame timestamps
35     mean_pixels_approximated: The mean percentage of pixels approximated
36     queueing_durations: The queueing delay between compositor & main threads
37
38   Note that if any of the interaction records provided to AddResults have less
39   than 2 frames, we will return telemetry values with None values for each of
40   the smoothness metrics. Similarly, older browsers without support for
41   tracking the BeginMainFrame events will report a ListOfScalarValues with a
42   None value for the queueing duration metric.
43   """
44
45   def __init__(self):
46     super(SmoothnessMetric, self).__init__()
47
48   def AddResults(self, model, renderer_thread, interaction_records, results):
49     self.VerifyNonOverlappedRecords(interaction_records)
50     renderer_process = renderer_thread.parent
51     stats = rendering_stats.RenderingStats(
52       renderer_process, model.browser_process,
53       [r.GetBounds() for r in interaction_records])
54     self._PopulateResultsFromStats(results, stats)
55
56   def _PopulateResultsFromStats(self, results, stats):
57     page = results.current_page
58     values = [
59         self._ComputeQueueingDuration(page, stats),
60         self._ComputeFrameTimeDiscrepancy(page, stats),
61         self._ComputeMeanPixelsApproximated(page, stats)
62     ]
63     values += self._ComputeLatencyMetric(page, stats, 'input_event_latency',
64                                          stats.input_event_latency)
65     values += self._ComputeLatencyMetric(page, stats, 'scroll_update_latency',
66                                          stats.scroll_update_latency)
67     values += self._ComputeFirstGestureScrollUpdateLatency(page, stats)
68     values += self._ComputeFrameTimeMetric(page, stats)
69     for v in values:
70       results.AddValue(v)
71
72   def _HasEnoughFrames(self, list_of_frame_timestamp_lists):
73     """Whether we have collected at least two frames in every timestamp list."""
74     return all(len(s) >= 2 for s in list_of_frame_timestamp_lists)
75
76   def _ComputeLatencyMetric(self, page, stats, name, list_of_latency_lists):
77     """Returns Values for the mean and discrepancy for given latency stats."""
78     mean_latency = None
79     latency_discrepancy = None
80     none_value_reason = None
81     if self._HasEnoughFrames(stats.frame_timestamps):
82       latency_list = FlattenList(list_of_latency_lists)
83       if len(latency_list) == 0:
84         return ()
85       mean_latency = round(statistics.ArithmeticMean(latency_list), 3)
86       latency_discrepancy = (
87           round(statistics.DurationsDiscrepancy(latency_list), 4))
88     else:
89       none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE
90     return (
91       scalar.ScalarValue(
92           page, 'mean_%s' % name, 'ms', mean_latency,
93           description='Arithmetic mean of the raw %s values' % name,
94           none_value_reason=none_value_reason),
95       scalar.ScalarValue(
96           page, '%s_discrepancy' % name, 'ms', latency_discrepancy,
97           description='Discrepancy of the raw %s values' % name,
98           none_value_reason=none_value_reason)
99     )
100
101   def _ComputeFirstGestureScrollUpdateLatency(self, page, stats):
102     """Returns a Value for the first gesture scroll update latency."""
103     first_gesture_scroll_update_latency = None
104     none_value_reason = None
105     if self._HasEnoughFrames(stats.frame_timestamps):
106       latency_list = FlattenList(stats.gesture_scroll_update_latency)
107       if len(latency_list) == 0:
108         return ()
109       first_gesture_scroll_update_latency = round(latency_list[0], 4)
110     else:
111       none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE
112     return (
113       scalar.ScalarValue(
114         page, 'first_gesture_scroll_update_latency', 'ms',
115         first_gesture_scroll_update_latency,
116         description='First gesture scroll update latency measures the time it '
117                     'takes to process the very first gesture scroll update '
118                     'input event. The first scroll gesture can often get '
119                     'delayed by work related to page loading.',
120         none_value_reason=none_value_reason),
121     )
122
123   def _ComputeQueueingDuration(self, page, stats):
124     """Returns a Value for the frame queueing durations."""
125     queueing_durations = None
126     none_value_reason = None
127     if 'frame_queueing_durations' in stats.errors:
128       none_value_reason = stats.errors['frame_queueing_durations']
129     elif self._HasEnoughFrames(stats.frame_timestamps):
130       queueing_durations = FlattenList(stats.frame_queueing_durations)
131       if len(queueing_durations) == 0:
132         queueing_durations = None
133         none_value_reason = 'No frame queueing durations recorded.'
134     else:
135       none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE
136     return list_of_scalar_values.ListOfScalarValues(
137         page, 'queueing_durations', 'ms', queueing_durations,
138         description='The frame queueing duration quantifies how out of sync '
139                     'the compositor and renderer threads are. It is the amount '
140                     'of wall time that elapses between a '
141                     'ScheduledActionSendBeginMainFrame event in the compositor '
142                     'thread and the corresponding BeginMainFrame event in the '
143                     'main thread.',
144         none_value_reason=none_value_reason)
145
146   def _ComputeFrameTimeMetric(self, page, stats):
147     """Returns Values for the frame time metrics.
148
149     This includes the raw and mean frame times, as well as the percentage of
150     frames that were hitting 60 fps.
151     """
152     frame_times = None
153     mean_frame_time = None
154     percentage_smooth = None
155     none_value_reason = None
156     if self._HasEnoughFrames(stats.frame_timestamps):
157       frame_times = FlattenList(stats.frame_times)
158       mean_frame_time = round(statistics.ArithmeticMean(frame_times), 3)
159       # We use 17ms as a somewhat looser threshold, instead of 1000.0/60.0.
160       smooth_threshold = 17.0
161       smooth_count = sum(1 for t in frame_times if t < smooth_threshold)
162       percentage_smooth = float(smooth_count) / len(frame_times) * 100.0
163     else:
164       none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE
165     return (
166         list_of_scalar_values.ListOfScalarValues(
167             page, 'frame_times', 'ms', frame_times,
168             description='List of raw frame times, helpful to understand the '
169                         'other metrics.',
170             none_value_reason=none_value_reason),
171         scalar.ScalarValue(
172             page, 'mean_frame_time', 'ms', mean_frame_time,
173             description='Arithmetic mean of frame times.',
174             none_value_reason=none_value_reason),
175         scalar.ScalarValue(
176             page, 'percentage_smooth', 'score', percentage_smooth,
177             description='Percentage of frames that were hitting 60 fps.',
178             none_value_reason=none_value_reason)
179     )
180
181   def _ComputeFrameTimeDiscrepancy(self, page, stats):
182     """Returns a Value for the absolute discrepancy of frame time stamps."""
183
184     frame_discrepancy = None
185     none_value_reason = None
186     if self._HasEnoughFrames(stats.frame_timestamps):
187       frame_discrepancy = round(statistics.TimestampsDiscrepancy(
188           stats.frame_timestamps), 4)
189     else:
190       none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE
191     return scalar.ScalarValue(
192         page, 'frame_time_discrepancy', 'ms', frame_discrepancy,
193         description='Absolute discrepancy of frame time stamps, where '
194                     'discrepancy is a measure of irregularity. It quantifies '
195                     'the worst jank. For a single pause, discrepancy '
196                     'corresponds to the length of this pause in milliseconds. '
197                     'Consecutive pauses increase the discrepancy. This metric '
198                     'is important because even if the mean and 95th '
199                     'percentile are good, one long pause in the middle of an '
200                     'interaction is still bad.',
201         none_value_reason=none_value_reason)
202
203   def _ComputeMeanPixelsApproximated(self, page, stats):
204     """Add the mean percentage of pixels approximated.
205
206     This looks at tiles which are missing or of low or non-ideal resolution.
207     """
208     mean_pixels_approximated = None
209     none_value_reason = None
210     if self._HasEnoughFrames(stats.frame_timestamps):
211       mean_pixels_approximated = round(statistics.ArithmeticMean(
212           FlattenList(stats.approximated_pixel_percentages)), 3)
213     else:
214       none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE
215     return scalar.ScalarValue(
216         page, 'mean_pixels_approximated', 'percent', mean_pixels_approximated,
217         description='Percentage of pixels that were approximated '
218                     '(checkerboarding, low-resolution tiles, etc.).',
219         none_value_reason=none_value_reason)