def _ComputeQueueingDuration(self, page, stats): """Returns a Value for the frame queueing durations.""" queueing_durations = None none_value_reason = None if 'frame_queueing_durations' in stats.errors: none_value_reason = stats.errors['frame_queueing_durations'] elif self._HasEnoughFrames(stats.frame_timestamps): queueing_durations = FlattenList(stats.frame_queueing_durations) if len(queueing_durations) == 0: queueing_durations = None none_value_reason = 'No frame queueing durations recorded.' else: none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE return list_of_scalar_values.ListOfScalarValues( page, 'queueing_durations', 'ms', queueing_durations, description='The frame queueing duration quantifies how out of sync ' 'the compositor and renderer threads are. It is the amount ' 'of wall time that elapses between a ' 'ScheduledActionSendBeginMainFrame event in the compositor ' 'thread and the corresponding BeginMainFrame event in the ' 'main thread.', none_value_reason=none_value_reason)
def _ComputeMeanPixelsCheckerboarded(self, page, stats): """Add the mean percentage of pixels checkerboarded. This looks at tiles which are only missing. It does not take into consideration tiles which are of low or non-ideal resolution. """ mean_pixels_checkerboarded = None none_value_reason = None if self._HasEnoughFrames(stats.frame_timestamps): if rendering_stats.CHECKERBOARDED_PIXEL_ERROR in stats.errors: none_value_reason = stats.errors[ rendering_stats.CHECKERBOARDED_PIXEL_ERROR] else: mean_pixels_checkerboarded = round( statistics.ArithmeticMean( FlattenList(stats.checkerboarded_pixel_percentages)), 3) else: none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE return scalar.ScalarValue( page, 'mean_pixels_checkerboarded', 'percent', mean_pixels_checkerboarded, description='Percentage of pixels that were checkerboarded.', none_value_reason=none_value_reason)
def _ComputeLatencyMetric(self, page, stats, name, list_of_latency_lists): """Returns Values for the mean and discrepancy for given latency stats.""" mean_latency = None latency_discrepancy = None none_value_reason = None if self._HasEnoughFrames(stats.frame_timestamps): latency_list = FlattenList(list_of_latency_lists) if len(latency_list) == 0: return () mean_latency = round(statistics.ArithmeticMean(latency_list), 3) latency_discrepancy = (round( statistics.DurationsDiscrepancy(latency_list), 4)) else: none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE return (scalar.ScalarValue( page, 'mean_%s' % name, 'ms', mean_latency, description='Arithmetic mean of the raw %s values' % name, none_value_reason=none_value_reason), scalar.ScalarValue( page, '%s_discrepancy' % name, 'ms', latency_discrepancy, description='Discrepancy of the raw %s values' % name, none_value_reason=none_value_reason))
def AddResults(self, model, renderer_thread, interaction_records, results): self.VerifyNonOverlappedRecords(interaction_records) renderer_process = renderer_thread.parent stats = rendering_stats.RenderingStats( renderer_process, model.browser_process, [r.GetBounds() for r in interaction_records]) input_event_latency = FlattenList(stats.input_event_latency) if input_event_latency: mean_input_event_latency = statistics.ArithmeticMean( input_event_latency) input_event_latency_discrepancy = statistics.DurationsDiscrepancy( input_event_latency) results.Add('mean_input_event_latency', 'ms', round(mean_input_event_latency, 3)) results.Add('input_event_latency_discrepancy', 'ms', round(input_event_latency_discrepancy, 4)) # List of raw frame times. frame_times = FlattenList(stats.frame_times) results.Add('frame_times', 'ms', frame_times) # Arithmetic mean of frame times. mean_frame_time = statistics.ArithmeticMean(frame_times) results.Add('mean_frame_time', 'ms', round(mean_frame_time, 3)) # Absolute discrepancy of frame time stamps. frame_discrepancy = statistics.TimestampsDiscrepancy( stats.frame_timestamps) results.Add('jank', 'ms', round(frame_discrepancy, 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(frame_times, 95.0) results.Add('mostly_smooth', 'score', 1.0 if percentile_95 < 19.0 else 0.0) # Mean percentage of pixels approximated (missing tiles, low resolution # tiles, non-ideal resolution tiles) results.Add( 'mean_pixels_approximated', 'percent', round( statistics.ArithmeticMean( FlattenList(stats.approximated_pixel_percentages)), 3))
def _ComputeFrameTimeMetric(self, page, stats): """Returns Values for the frame time metrics. This includes the raw and mean frame times, as well as the percentage of frames that were hitting 60 fps. """ frame_times = None mean_frame_time = None percentage_smooth = None none_value_reason = None if self._HasEnoughFrames(stats.frame_timestamps): frame_times = FlattenList(stats.frame_times) mean_frame_time = round(statistics.ArithmeticMean(frame_times), 3) # We use 17ms as a somewhat looser threshold, instead of 1000.0/60.0. smooth_threshold = 17.0 smooth_count = sum(1 for t in frame_times if t < smooth_threshold) percentage_smooth = float(smooth_count) / len(frame_times) * 100.0 else: none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE return (list_of_scalar_values.ListOfScalarValues( page, 'frame_times', 'ms', frame_times, description='List of raw frame times, helpful to understand the ' 'other metrics.', none_value_reason=none_value_reason), scalar.ScalarValue( page, 'mean_frame_time', 'ms', mean_frame_time, description='Arithmetic mean of frame times.', none_value_reason=none_value_reason), scalar.ScalarValue( page, 'percentage_smooth', 'score', percentage_smooth, description= 'Percentage of frames that were hitting 60 fps.', none_value_reason=none_value_reason))
def _ComputeFrameTimeMetric(self, page, stats): """Returns Values for the frame time metrics. This includes the raw and mean frame times, as well as the mostly_smooth metric which tracks whether we hit 60 fps for 95% of the frames. """ frame_times = None mean_frame_time = None mostly_smooth = None none_value_reason = None if self._HasEnoughFrames(stats.frame_timestamps): frame_times = FlattenList(stats.frame_times) mean_frame_time = round(statistics.ArithmeticMean(frame_times), 3) # We use 19ms as a somewhat looser threshold, instead of 1000.0/60.0. percentile_95 = statistics.Percentile(frame_times, 95.0) mostly_smooth = 1.0 if percentile_95 < 19.0 else 0.0 else: none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE return (list_of_scalar_values.ListOfScalarValues( page, 'frame_times', 'ms', frame_times, description='List of raw frame times, helpful to understand the ' 'other metrics.', none_value_reason=none_value_reason), scalar.ScalarValue( page, 'mean_frame_time', 'ms', mean_frame_time, description='Arithmetic mean of frame times.', none_value_reason=none_value_reason), scalar.ScalarValue( page, 'mostly_smooth', 'score', mostly_smooth, description='Were 95 percent of the frames hitting 60 fps?' 'boolean value (1/0).', none_value_reason=none_value_reason))
def _ComputeMeanPixelsApproximated(self, page, stats): """Add the mean percentage of pixels approximated. This looks at tiles which are missing or of low or non-ideal resolution. """ mean_pixels_approximated = None none_value_reason = None if self._HasEnoughFrames(stats.frame_timestamps): mean_pixels_approximated = round( statistics.ArithmeticMean( FlattenList(stats.approximated_pixel_percentages)), 3) else: none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE return scalar.ScalarValue( page, 'mean_pixels_approximated', 'percent', mean_pixels_approximated, description='Percentage of pixels that were approximated ' '(checkerboarding, low-resolution tiles, etc.).', none_value_reason=none_value_reason)
def _ComputeFirstGestureScrollUpdateLatency(self, page, stats): """Returns a Value for the first gesture scroll update latency.""" first_gesture_scroll_update_latency = None none_value_reason = None if self._HasEnoughFrames(stats.frame_timestamps): latency_list = FlattenList(stats.gesture_scroll_update_latency) if len(latency_list) == 0: return () first_gesture_scroll_update_latency = round(latency_list[0], 4) else: none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE return (scalar.ScalarValue( page, 'first_gesture_scroll_update_latency', 'ms', first_gesture_scroll_update_latency, description= 'First gesture scroll update latency measures the time it ' 'takes to process the very first gesture scroll update ' 'input event. The first scroll gesture can often get ' 'delayed by work related to page loading.', none_value_reason=none_value_reason), )
def testInputLatencyFromTimeline(self): timeline = model.TimelineModel() # Create a browser process and a renderer process. browser = timeline.GetOrCreateProcess(pid = 1) browser_main = browser.GetOrCreateThread(tid = 11) renderer = timeline.GetOrCreateProcess(pid = 2) renderer_main = renderer.GetOrCreateThread(tid = 21) timer = MockTimer() ref_latency = ReferenceInputLatencyStats() # Create 10 input latency stats events for Action A. timer.Advance(2, 4) renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '') for _ in xrange(0, 10): AddInputLatencyStats(timer, browser_main, renderer_main, ref_latency) timer.Advance(2, 4) renderer_main.EndSlice(timer.Get()) # Create 5 input latency stats events not within any action. timer.Advance(2, 4) for _ in xrange(0, 5): AddInputLatencyStats(timer, browser_main, renderer_main, None) # Create 10 input latency stats events for Action B. timer.Advance(2, 4) renderer_main.BeginSlice('webkit.console', 'ActionB', timer.Get(), '') for _ in xrange(0, 10): AddInputLatencyStats(timer, browser_main, renderer_main, ref_latency) timer.Advance(2, 4) renderer_main.EndSlice(timer.Get()) # Create 10 input latency stats events for Action A. timer.Advance(2, 4) renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '') for _ in xrange(0, 10): AddInputLatencyStats(timer, browser_main, renderer_main, ref_latency) timer.Advance(2, 4) renderer_main.EndSlice(timer.Get()) browser.FinalizeImport() renderer.FinalizeImport() input_events = [] timeline_markers = timeline.FindTimelineMarkers( ['ActionA', 'ActionB', 'ActionA']) timeline_ranges = [timeline_bounds.Bounds.CreateFromEvent(marker) for marker in timeline_markers] for timeline_range in timeline_ranges: if timeline_range.is_empty: continue input_events.extend(GetInputLatencyEvents(browser, timeline_range)) self.assertEquals(input_events, ref_latency.input_event) input_event_latency_result = ComputeInputEventLatencies(input_events) self.assertEquals(input_event_latency_result, ref_latency.input_event_latency) stats = RenderingStats(renderer, browser, timeline_ranges) self.assertEquals(FlattenList(stats.input_event_latency), [ latency for name, latency in ref_latency.input_event_latency if name != SCROLL_UPDATE_EVENT_NAME]) self.assertEquals(FlattenList(stats.scroll_update_latency), [ latency for name, latency in ref_latency.input_event_latency if name == SCROLL_UPDATE_EVENT_NAME]) self.assertEquals(FlattenList(stats.gesture_scroll_update_latency), [ latency for name, latency in ref_latency.input_event_latency if name == GESTURE_SCROLL_UPDATE_EVENT_NAME])
def _ComputeSurfaceFlingerMetric(self, page, stats): jank_count = None avg_surface_fps = None max_frame_delay = None frame_lengths = None none_value_reason = None if self._HasEnoughFrames(stats.frame_timestamps): timestamps = FlattenList(stats.frame_timestamps) frame_count = len(timestamps) milliseconds = timestamps[-1] - timestamps[0] min_normalized_frame_length = 0.5 frame_lengths, normalized_frame_lengths = \ self._GetNormalizedDeltas(timestamps, stats.refresh_period, min_normalized_frame_length) if len(frame_lengths) < frame_count - 1: logging.warning('Skipping frame lengths that are too short.') frame_count = len(frame_lengths) + 1 if len(frame_lengths) == 0: raise Exception('No valid frames lengths found.') _, normalized_changes = \ self._GetNormalizedDeltas(frame_lengths, stats.refresh_period) jankiness = [ max(0, round(change)) for change in normalized_changes ] pause_threshold = 20 jank_count = sum(1 for change in jankiness if change > 0 and change < pause_threshold) avg_surface_fps = int( round((frame_count - 1) * 1000.0 / milliseconds)) max_frame_delay = round(max(normalized_frame_lengths)) frame_lengths = normalized_frame_lengths else: none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE return ( scalar.ScalarValue( page, 'avg_surface_fps', 'fps', avg_surface_fps, description='Average frames per second as measured by the ' 'platform\'s SurfaceFlinger.', none_value_reason=none_value_reason), scalar.ScalarValue( page, 'jank_count', 'janks', jank_count, description='Number of changes in frame rate as measured by the ' 'platform\'s SurfaceFlinger.', none_value_reason=none_value_reason), scalar.ScalarValue( page, 'max_frame_delay', 'vsyncs', max_frame_delay, description='Largest frame time as measured by the platform\'s ' 'SurfaceFlinger.', none_value_reason=none_value_reason), list_of_scalar_values.ListOfScalarValues( page, 'frame_lengths', 'vsyncs', frame_lengths, description= 'Frame time in vsyncs as measured by the platform\'s ' 'SurfaceFlinger.', none_value_reason=none_value_reason))
def AddResults(self, model, renderer_thread, interaction_records, results): self.VerifyNonOverlappedRecords(interaction_records) renderer_process = renderer_thread.parent stats = rendering_stats.RenderingStats( renderer_process, model.browser_process, [r.GetBounds() for r in interaction_records]) input_event_latency = FlattenList(stats.input_event_latency) if input_event_latency: mean_input_event_latency = statistics.ArithmeticMean( input_event_latency) input_event_latency_discrepancy = statistics.DurationsDiscrepancy( input_event_latency) results.AddValue( scalar.ScalarValue(results.current_page, 'mean_input_event_latency', 'ms', round(mean_input_event_latency, 3))) results.AddValue( scalar.ScalarValue(results.current_page, 'input_event_latency_discrepancy', 'ms', round(input_event_latency_discrepancy, 4))) scroll_update_latency = FlattenList(stats.scroll_update_latency) if scroll_update_latency: mean_scroll_update_latency = statistics.ArithmeticMean( scroll_update_latency) scroll_update_latency_discrepancy = statistics.DurationsDiscrepancy( scroll_update_latency) results.AddValue( scalar.ScalarValue(results.current_page, 'mean_scroll_update_latency', 'ms', round(mean_scroll_update_latency, 3))) results.AddValue( scalar.ScalarValue(results.current_page, 'scroll_update_latency_discrepancy', 'ms', round(scroll_update_latency_discrepancy, 4))) gesture_scroll_update_latency = FlattenList( stats.gesture_scroll_update_latency) if gesture_scroll_update_latency: results.AddValue( scalar.ScalarValue(results.current_page, 'first_gesture_scroll_update_latency', 'ms', round(gesture_scroll_update_latency[0], 4))) # List of queueing durations. frame_queueing_durations = FlattenList(stats.frame_queueing_durations) if frame_queueing_durations: results.AddValue( list_of_scalar_values.ListOfScalarValues( results.current_page, 'queueing_durations', 'ms', frame_queueing_durations)) # List of raw frame times. frame_times = FlattenList(stats.frame_times) results.AddValue( list_of_scalar_values.ListOfScalarValues( results.current_page, 'frame_times', 'ms', frame_times, description= 'List of raw frame times, helpful to understand the other ' 'metrics.')) # Arithmetic mean of frame times. mean_frame_time = statistics.ArithmeticMean(frame_times) results.AddValue( scalar.ScalarValue(results.current_page, 'mean_frame_time', 'ms', round(mean_frame_time, 3), description='Arithmetic mean of frame times.')) # Absolute discrepancy of frame time stamps. frame_discrepancy = statistics.TimestampsDiscrepancy( stats.frame_timestamps) results.AddValue( scalar.ScalarValue( results.current_page, 'jank', 'ms', round(frame_discrepancy, 4), description='Absolute discrepancy of frame time stamps, where ' 'discrepancy is a measure of irregularity. It quantifies ' 'the worst jank. For a single pause, discrepancy ' 'corresponds to the length of this pause in milliseconds. ' 'Consecutive pauses increase the discrepancy. This metric ' 'is important because even if the mean and 95th ' 'percentile are good, one long pause in the middle of an ' 'interaction is still bad.')) # 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(frame_times, 95.0) results.AddValue( scalar.ScalarValue( results.current_page, 'mostly_smooth', 'score', 1.0 if percentile_95 < 19.0 else 0.0, description='Were 95 percent of the frames hitting 60 fps?' 'boolean value (1/0).')) # Mean percentage of pixels approximated (missing tiles, low resolution # tiles, non-ideal resolution tiles). results.AddValue( scalar.ScalarValue( results.current_page, 'mean_pixels_approximated', 'percent', round( statistics.ArithmeticMean( FlattenList(stats.approximated_pixel_percentages)), 3), description='Percentage of pixels that were approximated ' '(checkerboarding, low-resolution tiles, etc.).'))