OLD | NEW |
1 # Copyright 2013 The Chromium Authors. All rights reserved. | 1 # Copyright 2013 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 from metrics import Metric | 5 from metrics import timeline_based_metric |
6 from metrics import rendering_stats | 6 from metrics import rendering_stats |
7 from telemetry.page import page_measurement | |
8 from telemetry.page.perf_tests_helper import FlattenList | 7 from telemetry.page.perf_tests_helper import FlattenList |
9 from telemetry.util import statistics | 8 from telemetry.util import statistics |
10 from telemetry.core.timeline.model import TimelineModel | 9 from telemetry.core.timeline import bounds |
11 | |
12 TIMELINE_MARKER = 'Smoothness' | |
13 | 10 |
14 | 11 |
15 class MissingDisplayFrameRateError(page_measurement.MeasurementFailure): | 12 class SmoothnessMetric(timeline_based_metric.TimelineBasedMetric): |
16 def __init__(self, name): | |
17 super(MissingDisplayFrameRateError, self).__init__( | |
18 'Missing display frame rate metrics: ' + name) | |
19 | |
20 class NotEnoughFramesError(page_measurement.MeasurementFailure): | |
21 def __init__(self): | |
22 super(NotEnoughFramesError, self).__init__( | |
23 'Page output less than two frames') | |
24 | |
25 | |
26 class NoSupportedActionError(page_measurement.MeasurementFailure): | |
27 def __init__(self): | |
28 super(NoSupportedActionError, self).__init__( | |
29 'None of the actions is supported by smoothness measurement') | |
30 | |
31 | |
32 def _GetSyntheticDelayCategoriesFromPage(page): | |
33 if not hasattr(page, 'synthetic_delays'): | |
34 return [] | |
35 result = [] | |
36 for delay, options in page.synthetic_delays.items(): | |
37 options = '%f;%s' % (options.get('target_duration', 0), | |
38 options.get('mode', 'static')) | |
39 result.append('DELAY(%s;%s)' % (delay, options)) | |
40 return result | |
41 | |
42 | |
43 class SmoothnessMetric(Metric): | |
44 def __init__(self): | 13 def __init__(self): |
45 super(SmoothnessMetric, self).__init__() | 14 super(SmoothnessMetric, self).__init__() |
46 self._stats = None | |
47 self._actions = [] | |
48 | 15 |
49 def AddActionToIncludeInMetric(self, action): | 16 def AddResults(self, model, renderer_thread, interaction_record, results): |
50 self._actions.append(action) | 17 renderer_process = renderer_thread.parent |
51 | 18 time_bounds = bounds.Bounds() |
52 def Start(self, page, tab): | 19 time_bounds.AddValue(interaction_record.start) |
53 custom_categories = ['webkit.console', 'benchmark'] | 20 time_bounds.AddValue(interaction_record.end) |
54 custom_categories += _GetSyntheticDelayCategoriesFromPage(page) | 21 stats = rendering_stats.RenderingStats( |
55 tab.browser.StartTracing(','.join(custom_categories), 60) | 22 renderer_process, model.browser_process, [time_bounds]) |
56 tab.ExecuteJavaScript('console.time("' + TIMELINE_MARKER + '")') | 23 if stats.mouse_wheel_scroll_latency: |
57 if tab.browser.platform.IsRawDisplayFrameRateSupported(): | |
58 tab.browser.platform.StartRawDisplayFrameRateMeasurement() | |
59 | |
60 def Stop(self, page, tab): | |
61 if tab.browser.platform.IsRawDisplayFrameRateSupported(): | |
62 tab.browser.platform.StopRawDisplayFrameRateMeasurement() | |
63 tab.ExecuteJavaScript('console.timeEnd("' + TIMELINE_MARKER + '")') | |
64 tracing_timeline_data = tab.browser.StopTracing() | |
65 timeline_model = TimelineModel(timeline_data=tracing_timeline_data) | |
66 timeline_ranges = [ action.GetActiveRangeOnTimeline(timeline_model) | |
67 for action in self._actions ] | |
68 | |
69 renderer_process = timeline_model.GetRendererProcessFromTab(tab) | |
70 self._stats = rendering_stats.RenderingStats( | |
71 renderer_process, timeline_model.browser_process, timeline_ranges) | |
72 | |
73 if not self._stats.frame_times: | |
74 raise NotEnoughFramesError() | |
75 | |
76 def AddResults(self, tab, results): | |
77 if self._stats.mouse_wheel_scroll_latency: | |
78 mean_mouse_wheel_scroll_latency = statistics.ArithmeticMean( | 24 mean_mouse_wheel_scroll_latency = statistics.ArithmeticMean( |
79 self._stats.mouse_wheel_scroll_latency) | 25 stats.mouse_wheel_scroll_latency) |
80 mouse_wheel_scroll_latency_discrepancy = statistics.DurationsDiscrepancy( | 26 mouse_wheel_scroll_latency_discrepancy = statistics.DurationsDiscrepancy( |
81 self._stats.mouse_wheel_scroll_latency) | 27 stats.mouse_wheel_scroll_latency) |
82 results.Add('mean_mouse_wheel_scroll_latency', 'ms', | 28 results.Add('mean_mouse_wheel_scroll_latency', 'ms', |
83 round(mean_mouse_wheel_scroll_latency, 3)) | 29 round(mean_mouse_wheel_scroll_latency, 3)) |
84 results.Add('mouse_wheel_scroll_latency_discrepancy', '', | 30 results.Add('mouse_wheel_scroll_latency_discrepancy', '', |
85 round(mouse_wheel_scroll_latency_discrepancy, 4)) | 31 round(mouse_wheel_scroll_latency_discrepancy, 4)) |
86 | 32 |
87 if self._stats.touch_scroll_latency: | 33 if stats.touch_scroll_latency: |
88 mean_touch_scroll_latency = statistics.ArithmeticMean( | 34 mean_touch_scroll_latency = statistics.ArithmeticMean( |
89 self._stats.touch_scroll_latency) | 35 stats.touch_scroll_latency) |
90 touch_scroll_latency_discrepancy = statistics.DurationsDiscrepancy( | 36 touch_scroll_latency_discrepancy = statistics.DurationsDiscrepancy( |
91 self._stats.touch_scroll_latency) | 37 stats.touch_scroll_latency) |
92 results.Add('mean_touch_scroll_latency', 'ms', | 38 results.Add('mean_touch_scroll_latency', 'ms', |
93 round(mean_touch_scroll_latency, 3)) | 39 round(mean_touch_scroll_latency, 3)) |
94 results.Add('touch_scroll_latency_discrepancy', '', | 40 results.Add('touch_scroll_latency_discrepancy', '', |
95 round(touch_scroll_latency_discrepancy, 4)) | 41 round(touch_scroll_latency_discrepancy, 4)) |
96 | 42 |
97 if self._stats.js_touch_scroll_latency: | 43 if stats.js_touch_scroll_latency: |
98 mean_js_touch_scroll_latency = statistics.ArithmeticMean( | 44 mean_js_touch_scroll_latency = statistics.ArithmeticMean( |
99 self._stats.js_touch_scroll_latency) | 45 stats.js_touch_scroll_latency) |
100 js_touch_scroll_latency_discrepancy = statistics.DurationsDiscrepancy( | 46 js_touch_scroll_latency_discrepancy = statistics.DurationsDiscrepancy( |
101 self._stats.js_touch_scroll_latency) | 47 stats.js_touch_scroll_latency) |
102 results.Add('mean_js_touch_scroll_latency', 'ms', | 48 results.Add('mean_js_touch_scroll_latency', 'ms', |
103 round(mean_js_touch_scroll_latency, 3)) | 49 round(mean_js_touch_scroll_latency, 3)) |
104 results.Add('js_touch_scroll_latency_discrepancy', '', | 50 results.Add('js_touch_scroll_latency_discrepancy', '', |
105 round(js_touch_scroll_latency_discrepancy, 4)) | 51 round(js_touch_scroll_latency_discrepancy, 4)) |
106 | 52 |
107 # List of raw frame times. | 53 # List of raw frame times. |
108 frame_times = FlattenList(self._stats.frame_times) | 54 frame_times = FlattenList(stats.frame_times) |
109 results.Add('frame_times', 'ms', frame_times) | 55 results.Add('frame_times', 'ms', frame_times) |
110 | 56 |
111 # Arithmetic mean of frame times. | 57 # Arithmetic mean of frame times. |
112 mean_frame_time = statistics.ArithmeticMean(frame_times) | 58 mean_frame_time = statistics.ArithmeticMean(frame_times) |
113 results.Add('mean_frame_time', 'ms', round(mean_frame_time, 3)) | 59 results.Add('mean_frame_time', 'ms', round(mean_frame_time, 3)) |
114 | 60 |
115 # Absolute discrepancy of frame time stamps. | 61 # Absolute discrepancy of frame time stamps. |
116 frame_discrepancy = statistics.TimestampsDiscrepancy( | 62 frame_discrepancy = statistics.TimestampsDiscrepancy( |
117 self._stats.frame_timestamps) | 63 stats.frame_timestamps) |
118 results.Add('jank', 'ms', round(frame_discrepancy, 4)) | 64 results.Add('jank', 'ms', round(frame_discrepancy, 4)) |
119 | 65 |
120 # Are we hitting 60 fps for 95 percent of all frames? | 66 # Are we hitting 60 fps for 95 percent of all frames? |
121 # We use 19ms as a somewhat looser threshold, instead of 1000.0/60.0. | 67 # We use 19ms as a somewhat looser threshold, instead of 1000.0/60.0. |
122 percentile_95 = statistics.Percentile(frame_times, 95.0) | 68 percentile_95 = statistics.Percentile(frame_times, 95.0) |
123 results.Add('mostly_smooth', 'score', 1.0 if percentile_95 < 19.0 else 0.0) | 69 results.Add('mostly_smooth', 'score', 1.0 if percentile_95 < 19.0 else 0.0) |
124 | |
125 if tab.browser.platform.IsRawDisplayFrameRateSupported(): | |
126 for r in tab.browser.platform.GetRawDisplayFrameRateMeasurements(): | |
127 if r.value is None: | |
128 raise MissingDisplayFrameRateError(r.name) | |
129 results.Add(r.name, r.unit, r.value) | |
OLD | NEW |