Chromium Code Reviews| 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 metrics import statistics | 7 from metrics import statistics |
| 8 from telemetry.core.timeline import bounds | |
| 8 from telemetry.page import page_measurement | 9 from telemetry.page import page_measurement |
| 9 from telemetry.page.perf_tests_helper import FlattenList | 10 from telemetry.page.perf_tests_helper import FlattenList |
| 10 from telemetry.core.timeline.model import TimelineModel | |
| 11 | 11 |
| 12 TIMELINE_MARKER = 'Smoothness' | |
| 13 | |
| 14 | |
| 15 class MissingDisplayFrameRateError(page_measurement.MeasurementFailure): | |
| 16 def __init__(self, name): | |
| 17 super(MissingDisplayFrameRateError, self).__init__( | |
| 18 'Missing display frame rate metrics: ' + name) | |
| 19 | 12 |
| 20 class NotEnoughFramesError(page_measurement.MeasurementFailure): | 13 class NotEnoughFramesError(page_measurement.MeasurementFailure): |
| 21 def __init__(self): | 14 def __init__(self): |
| 22 super(NotEnoughFramesError, self).__init__( | 15 super(NotEnoughFramesError, self).__init__( |
| 23 'Page output less than two frames') | 16 'Page output less than two frames') |
| 24 | 17 |
| 25 | 18 |
| 26 class NoSupportedActionError(page_measurement.MeasurementFailure): | 19 class NoSupportedActionError(page_measurement.MeasurementFailure): |
| 27 def __init__(self): | 20 def __init__(self): |
| 28 super(NoSupportedActionError, self).__init__( | 21 super(NoSupportedActionError, self).__init__( |
| 29 'None of the actions is supported by smoothness measurement') | 22 'None of the actions is supported by smoothness measurement') |
| 30 | 23 |
| 31 | 24 class SmoothnessMetric(timeline_based_metric.TimelineBasedMetric): |
| 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): | 25 def __init__(self): |
| 45 super(SmoothnessMetric, self).__init__() | 26 super(SmoothnessMetric, self).__init__() |
| 46 self._stats = None | |
| 47 self._actions = [] | |
| 48 | 27 |
| 49 def AddActionToIncludeInMetric(self, action): | 28 def AddResults(self, model, renderer_thread, interaction_record, results): |
| 50 self._actions.append(action) | 29 renderer_process = renderer_thread.parent |
| 51 | 30 time_bounds = bounds.Bounds() |
| 52 def Start(self, page, tab): | 31 time_bounds.AddValue(interaction_record.start) |
| 53 custom_categories = ['webkit.console', 'benchmark'] | 32 time_bounds.AddValue(interaction_record.end) |
| 54 custom_categories += _GetSyntheticDelayCategoriesFromPage(page) | 33 stats = rendering_stats.RenderingStats( |
| 55 tab.browser.StartTracing(','.join(custom_categories), 60) | 34 renderer_process, model.browser_process, [time_bounds]) |
| 56 tab.ExecuteJavaScript('console.time("' + TIMELINE_MARKER + '")') | 35 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( | 36 mean_mouse_wheel_scroll_latency = statistics.ArithmeticMean( |
| 79 self._stats.mouse_wheel_scroll_latency, | 37 stats.mouse_wheel_scroll_latency, |
| 80 len(self._stats.mouse_wheel_scroll_latency)) | 38 len(stats.mouse_wheel_scroll_latency)) |
| 81 mouse_wheel_scroll_latency_discrepancy = statistics.DurationsDiscrepancy( | 39 mouse_wheel_scroll_latency_discrepancy = statistics.DurationsDiscrepancy( |
| 82 self._stats.mouse_wheel_scroll_latency) | 40 stats.mouse_wheel_scroll_latency) |
| 83 results.Add('mean_mouse_wheel_scroll_latency', 'ms', | 41 results.Add('mean_mouse_wheel_scroll_latency', 'ms', |
| 84 round(mean_mouse_wheel_scroll_latency, 3)) | 42 round(mean_mouse_wheel_scroll_latency, 3)) |
| 85 results.Add('mouse_wheel_scroll_latency_discrepancy', '', | 43 results.Add('mouse_wheel_scroll_latency_discrepancy', '', |
| 86 round(mouse_wheel_scroll_latency_discrepancy, 4)) | 44 round(mouse_wheel_scroll_latency_discrepancy, 4)) |
| 87 | 45 |
| 88 if self._stats.touch_scroll_latency: | 46 if stats.touch_scroll_latency: |
| 89 mean_touch_scroll_latency = statistics.ArithmeticMean( | 47 mean_touch_scroll_latency = statistics.ArithmeticMean( |
| 90 self._stats.touch_scroll_latency, | 48 stats.touch_scroll_latency, |
| 91 len(self._stats.touch_scroll_latency)) | 49 len(stats.touch_scroll_latency)) |
| 92 touch_scroll_latency_discrepancy = statistics.DurationsDiscrepancy( | 50 touch_scroll_latency_discrepancy = statistics.DurationsDiscrepancy( |
| 93 self._stats.touch_scroll_latency) | 51 stats.touch_scroll_latency) |
| 94 results.Add('mean_touch_scroll_latency', 'ms', | 52 results.Add('mean_touch_scroll_latency', 'ms', |
| 95 round(mean_touch_scroll_latency, 3)) | 53 round(mean_touch_scroll_latency, 3)) |
| 96 results.Add('touch_scroll_latency_discrepancy', '', | 54 results.Add('touch_scroll_latency_discrepancy', '', |
| 97 round(touch_scroll_latency_discrepancy, 4)) | 55 round(touch_scroll_latency_discrepancy, 4)) |
| 98 | 56 |
| 99 if self._stats.js_touch_scroll_latency: | 57 if stats.js_touch_scroll_latency: |
| 100 mean_js_touch_scroll_latency = statistics.ArithmeticMean( | 58 mean_js_touch_scroll_latency = statistics.ArithmeticMean( |
| 101 self._stats.js_touch_scroll_latency, | 59 stats.js_touch_scroll_latency, |
| 102 len(self._stats.js_touch_scroll_latency)) | 60 len(stats.js_touch_scroll_latency)) |
| 103 js_touch_scroll_latency_discrepancy = statistics.DurationsDiscrepancy( | 61 js_touch_scroll_latency_discrepancy = statistics.DurationsDiscrepancy( |
| 104 self._stats.js_touch_scroll_latency) | 62 stats.js_touch_scroll_latency) |
| 105 results.Add('mean_js_touch_scroll_latency', 'ms', | 63 results.Add('mean_js_touch_scroll_latency', 'ms', |
| 106 round(mean_js_touch_scroll_latency, 3)) | 64 round(mean_js_touch_scroll_latency, 3)) |
| 107 results.Add('js_touch_scroll_latency_discrepancy', '', | 65 results.Add('js_touch_scroll_latency_discrepancy', '', |
| 108 round(js_touch_scroll_latency_discrepancy, 4)) | 66 round(js_touch_scroll_latency_discrepancy, 4)) |
| 109 | 67 |
| 110 # List of raw frame times. | 68 # List of raw frame times. |
| 111 frame_times = FlattenList(self._stats.frame_times) | 69 frame_times = FlattenList(stats.frame_times) |
| 112 results.Add('frame_times', 'ms', frame_times) | 70 results.Add('frame_times', 'ms', frame_times) |
| 113 | 71 |
| 114 # Arithmetic mean of frame times. | 72 # Arithmetic mean of frame times. |
| 115 mean_frame_time = statistics.ArithmeticMean(frame_times, | 73 mean_frame_time = statistics.ArithmeticMean(frame_times, |
| 116 len(frame_times)) | 74 len(frame_times)) |
| 117 results.Add('mean_frame_time', 'ms', round(mean_frame_time, 3)) | 75 results.Add('mean_frame_time', 'ms', round(mean_frame_time, 3)) |
| 118 | 76 |
| 119 # Absolute discrepancy of frame time stamps. | 77 # Absolute discrepancy of frame time stamps. |
| 120 frame_discrepancy = statistics.TimestampsDiscrepancy( | 78 frame_discrepancy = statistics.TimestampsDiscrepancy( |
| 121 self._stats.frame_timestamps) | 79 stats.frame_timestamps) |
| 122 results.Add('jank', 'ms', round(frame_discrepancy, 4)) | 80 results.Add('jank', 'ms', round(frame_discrepancy, 4)) |
| 123 | 81 |
| 124 # Are we hitting 60 fps for 95 percent of all frames? | 82 # Are we hitting 60 fps for 95 percent of all frames? |
| 125 # We use 19ms as a somewhat looser threshold, instead of 1000.0/60.0. | 83 # We use 19ms as a somewhat looser threshold, instead of 1000.0/60.0. |
|
nduca
2014/03/12 18:07:24
lets file a followup bug and cc:you cc:ernstm to l
| |
| 126 percentile_95 = statistics.Percentile(frame_times, 95.0) | 84 percentile_95 = statistics.Percentile(frame_times, 95.0) |
| 127 results.Add('mostly_smooth', 'score', 1.0 if percentile_95 < 19.0 else 0.0) | 85 results.Add('mostly_smooth', 'score', 1.0 if percentile_95 < 19.0 else 0.0) |
| 128 | |
| 129 if tab.browser.platform.IsRawDisplayFrameRateSupported(): | |
| 130 for r in tab.browser.platform.GetRawDisplayFrameRateMeasurements(): | |
| 131 if r.value is None: | |
| 132 raise MissingDisplayFrameRateError(r.name) | |
| 133 results.Add(r.name, r.unit, r.value) | |
| OLD | NEW |