# Copyright 2013 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. from metrics import Metric from metrics import rendering_stats from metrics import statistics from telemetry.core.timeline.model import MarkerMismatchError from telemetry.core.timeline.model import MarkerOverlapError from telemetry.page import page_measurement TIMELINE_MARKER = 'Smoothness' class NotEnoughFramesError(page_measurement.MeasurementFailure): def __init__(self): super(NotEnoughFramesError, self).__init__( 'Page output less than two frames') class NoSupportedActionError(page_measurement.MeasurementFailure): def __init__(self): super(NoSupportedActionError, self).__init__( 'None of the actions is supported by smoothness measurement') class SmoothnessMetric(Metric): def __init__(self): super(SmoothnessMetric, self).__init__() self._stats = None self._timeline_marker_names = [] def AddTimelineMarkerNameToIncludeInMetric(self, timeline_marker_name): self._timeline_marker_names.append(timeline_marker_name) def Start(self, page, tab): tab.browser.StartTracing('webkit.console,benchmark', 60) tab.ExecuteJavaScript('console.time("' + TIMELINE_MARKER + '")') def Stop(self, page, tab): tab.ExecuteJavaScript('console.timeEnd("' + TIMELINE_MARKER + '")') timeline_model = tab.browser.StopTracing().AsTimelineModel() try: timeline_markers = timeline_model.FindTimelineMarkers( self._timeline_marker_names) except MarkerMismatchError as e: raise page_measurement.MeasurementFailure(str(e)) except MarkerOverlapError as e: raise page_measurement.MeasurementFailure(str(e)) renderer_process = timeline_model.GetRendererProcessFromTab(tab) self._stats = rendering_stats.RenderingStats( renderer_process, timeline_markers) if not self._stats.frame_times: raise NotEnoughFramesError() def SetStats(self, stats): """ Pass in a RenderingStats object directly. For unittests that don't call Start/Stop. """ self._stats = stats def AddResults(self, tab, results): # List of raw frame times. results.Add('frame_times', 'ms', self._stats.frame_times) # Arithmetic mean of frame times. mean_frame_time = statistics.ArithmeticMean(self._stats.frame_times, len(self._stats.frame_times)) results.Add('mean_frame_time', 'ms', round(mean_frame_time, 3)) # Absolute discrepancy of frame time stamps. jank = statistics.FrameDiscrepancy(self._stats.frame_timestamps) results.Add('jank', '', round(jank, 4)) # Are we hitting 60 fps for 95 percent of all frames? # We use 19ms as a somewhat looser threshold, instead of 1000.0/60.0. percentile_95 = statistics.Percentile(self._stats.frame_times, 95.0) results.Add('mostly_smooth', '', 1.0 if percentile_95 < 19.0 else 0.0)