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 3 # found in the LICENSE file. 4 5 from metrics import Metric 6 from metrics import rendering_stats 7 from metrics import statistics 8 from telemetry.core.timeline.model import MarkerMismatchError 9 from telemetry.core.timeline.model import MarkerOverlapError 10 from telemetry.page import page_measurement 11 12 TIMELINE_MARKER = 'Smoothness' 13 14 15 class NotEnoughFramesError(page_measurement.MeasurementFailure): 16 def __init__(self): 17 super(NotEnoughFramesError, self).__init__( 18 'Page output less than two frames') 19 20 21 class NoSupportedActionError(page_measurement.MeasurementFailure): 22 def __init__(self): 23 super(NoSupportedActionError, self).__init__( 24 'None of the actions is supported by smoothness measurement') 25 26 27 class SmoothnessMetric(Metric): 28 def __init__(self): 29 super(SmoothnessMetric, self).__init__() 30 self._stats = None 31 self._timeline_marker_names = [] 32 33 def AddTimelineMarkerNameToIncludeInMetric(self, timeline_marker_name): 34 self._timeline_marker_names.append(timeline_marker_name) 35 36 def Start(self, page, tab): 37 tab.browser.StartTracing('webkit.console,benchmark', 60) 38 tab.ExecuteJavaScript('console.time("' + TIMELINE_MARKER + '")') 39 40 def Stop(self, page, tab): 41 tab.ExecuteJavaScript('console.timeEnd("' + TIMELINE_MARKER + '")') 42 timeline_model = tab.browser.StopTracing().AsTimelineModel() 43 try: 44 timeline_markers = timeline_model.FindTimelineMarkers( 45 self._timeline_marker_names) 46 except MarkerMismatchError as e: 47 raise page_measurement.MeasurementFailure(str(e)) 48 except MarkerOverlapError as e: 49 raise page_measurement.MeasurementFailure(str(e)) 50 51 renderer_process = timeline_model.GetRendererProcessFromTab(tab) 52 self._stats = rendering_stats.RenderingStats( 53 renderer_process, timeline_markers) 54 55 if not self._stats.frame_times: 56 raise NotEnoughFramesError() 57 58 def SetStats(self, stats): 59 """ Pass in a RenderingStats object directly. For unittests that don't call 60 Start/Stop. 61 """ 62 self._stats = stats 63 64 def AddResults(self, tab, results): 65 # List of raw frame times. 66 results.Add('frame_times', 'ms', self._stats.frame_times) 67 68 # Arithmetic mean of frame times. 69 mean_frame_time = statistics.ArithmeticMean(self._stats.frame_times, 70 len(self._stats.frame_times)) 71 results.Add('mean_frame_time', 'ms', round(mean_frame_time, 3)) 72 73 # Absolute discrepancy of frame time stamps. 74 jank = statistics.FrameDiscrepancy(self._stats.frame_timestamps) 75 results.Add('jank', '', round(jank, 4)) 76 77 # Are we hitting 60 fps for 95 percent of all frames? 78 # We use 19ms as a somewhat looser threshold, instead of 1000.0/60.0. 79 percentile_95 = statistics.Percentile(self._stats.frame_times, 95.0) 80 results.Add('mostly_smooth', '', 1.0 if percentile_95 < 19.0 else 0.0) 81