def testDisplayStats(self): timeline = model.TimelineModel() timer = MockTimer() ref_stats = ReferenceRenderingStats() ref_stats.AppendNewRange() renderer = timeline.GetOrCreateProcess(pid=2) browser = timeline.GetOrCreateProcess(pid=3) browser_main = browser.GetOrCreateThread(tid=31) browser_main.BeginSlice('webkit.console', 'ActionA', timer.AdvanceAndGet(2, 4), '') # Create display rendering stats. for i in xrange(0, 10): first = (i == 0) AddDisplayRenderingStats(timer, browser_main, first, ref_stats) timer.Advance(5, 10) browser_main.EndSlice(timer.AdvanceAndGet()) timer.Advance(2, 4) browser.FinalizeImport() timeline_markers = timeline.FindTimelineMarkers(['ActionA']) records = [ tir_module.TimelineInteractionRecord(e.name, e.start, e.end) for e in timeline_markers ] stats = rendering_stats.RenderingStats(renderer, browser, None, records) # Compare rendering stats to reference - Only display stats should count self.assertEquals(stats.frame_timestamps, ref_stats.frame_timestamps) self.assertEquals(stats.frame_times, ref_stats.frame_times)
def testRangeWithoutFrames(self): timer = MockTimer() timeline = model.TimelineModel() # Create a renderer process, with a main thread and impl thread. renderer = timeline.GetOrCreateProcess(pid=2) renderer_main = renderer.GetOrCreateThread(tid=21) renderer_main.BeginSlice('webkit.console', 'ActionA', timer.AdvanceAndGet(2, 4), '') renderer_main.EndSlice(timer.AdvanceAndGet(2, 4)) timer.Advance(2, 4) # Create Action B without any frames. This should trigger # NotEnoughFramesError when the RenderingStats object is created. renderer_main.BeginSlice('webkit.console', 'ActionB', timer.AdvanceAndGet(2, 4), '') renderer_main.EndSlice(timer.AdvanceAndGet(2, 4)) renderer.FinalizeImport() timeline_markers = timeline.FindTimelineMarkers(['ActionA', 'ActionB']) records = [ tir_module.TimelineInteractionRecord(e.name, e.start, e.end) for e in timeline_markers ] stats = rendering_stats.RenderingStats(renderer, None, None, records) self.assertEquals(0, len(stats.frame_timestamps[1]))
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]) self._PopulateResultsFromStats(results, stats)
class SmoothnessMetric(timeline_based_metric.TimelineBasedMetric): """Computes metrics that measure smoothness of animations over given ranges. Animations are typically considered smooth if the frame rates are close to 60 frames per second (fps) and uniformly distributed over the sequence. To determine if a timeline range contains a smooth animation, we update the results object with several representative metrics: frame_times: A list of raw frame times mean_frame_time: The arithmetic mean of frame times percentage_smooth: Percentage of frames that were hitting 60 FPS. frame_time_discrepancy: The absolute discrepancy of frame timestamps mean_pixels_approximated: The mean percentage of pixels approximated queueing_durations: The queueing delay between compositor & presentation.main threads Note that if any of the interaction records provided to AddResults have less than 2 frames, we will return telemetry values with None values for each of the smoothness metrics. Similarly, older browsers without support for tracking the BeginMainFrame events will report a ListOfScalarValues with a None value for the queueing duration metric. """ def __init__(self): super(SmoothnessMetric, self).__init__() def AddResults(self, domain.model, renderer_thread, interaction_records, results): self.VerifyNonOverlappedRecords(interaction_records) renderer_process = renderer_thread.parent stats = rendering_stats.RenderingStats( renderer_process, domain.model.browser_process, domain.model.surface_flinger_process, domain.model.gpu_process, [r.GetBounds() for r in interaction_records]) has_surface_flinger_stats = domain.model.surface_flinger_process is not None self._PopulateResultsFromStats(results, stats, has_surface_flinger_stats)
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, model.gpu_process, interaction_records) has_ui_interactions = any( [r.label.startswith("ui_") for r in interaction_records]) self._PopulateResultsFromStats(results, stats, has_ui_interactions)
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, model.surface_flinger_process, [r.GetBounds() for r in interaction_records]) has_surface_flinger_stats = model.surface_flinger_process is not None self._PopulateResultsFromStats(results, stats, has_surface_flinger_stats, interaction_records[0].label)
def testBothSurfaceFlingerAndDisplayStats(self): timeline = model.TimelineModel() timer = MockTimer() ref_stats = ReferenceRenderingStats() ref_stats.AppendNewRange() surface_flinger = timeline.GetOrCreateProcess(pid=4) surface_flinger.name = 'SurfaceFlinger' surface_flinger_thread = surface_flinger.GetOrCreateThread(tid=41) renderer = timeline.GetOrCreateProcess(pid=2) browser = timeline.GetOrCreateProcess(pid=3) browser_main = browser.GetOrCreateThread(tid=31) browser_main.BeginSlice('webkit.console', 'ActionA', timer.AdvanceAndGet(2, 4), '') # Create SurfaceFlinger stats and display rendering stats. for i in xrange(0, 10): first = (i == 0) AddSurfaceFlingerStats(timer, surface_flinger_thread, first, ref_stats) timer.Advance(2, 4) for i in xrange(0, 10): first = (i == 0) AddDisplayRenderingStats(timer, browser_main, first, None) timer.Advance(5, 10) browser_main.EndSlice(timer.AdvanceAndGet()) timer.Advance(2, 4) browser.FinalizeImport() renderer.FinalizeImport() timeline_markers = timeline.FindTimelineMarkers(['ActionA']) records = [ tir_module.TimelineInteractionRecord(e.name, e.start, e.end) for e in timeline_markers ] metadata = [{ 'name': 'metadata', 'value': { 'surface_flinger': { 'refresh_period': 16.6666 } } }] stats = rendering_stats.RenderingStats(renderer, browser, surface_flinger, None, records, metadata) # Compare rendering stats to reference - Only SurfaceFlinger stats should # count self.assertEquals(stats.frame_timestamps, ref_stats.frame_timestamps) self.assertEquals(stats.frame_times, ref_stats.frame_times)
def testBothDrmAndDisplayStats(self): timeline = model.TimelineModel() timer = MockTimer() vblank_timer = MockVblankTimer() ref_stats = ReferenceRenderingStats() ref_stats.AppendNewRange() gpu = timeline.GetOrCreateProcess(pid=6) gpu.name = 'GPU Process' gpu_drm_thread = gpu.GetOrCreateThread(tid=61) renderer = timeline.GetOrCreateProcess(pid=2) browser = timeline.GetOrCreateProcess(pid=3) browser_main = browser.GetOrCreateThread(tid=31) browser_main.BeginSlice('webkit.console', 'ActionA', timer.AdvanceAndGet(2, 4), '') vblank_timer.TvAdvance(2000, 4000) # Create drm flip stats and display rendering stats. for i in xrange(0, 10): first = (i == 0) AddDrmEventFlipStats(timer, vblank_timer, gpu_drm_thread, first, ref_stats) timer.Advance(2, 4) vblank_timer.TvAdvance(2000, 4000) for i in xrange(0, 10): first = (i == 0) AddDisplayRenderingStats(timer, browser_main, first, None) timer.Advance(5, 10) vblank_timer.TvAdvance(5000, 10000) browser_main.EndSlice(timer.AdvanceAndGet()) timer.Advance(2, 4) vblank_timer.TvAdvance(2000, 4000) browser.FinalizeImport() renderer.FinalizeImport() timeline_markers = timeline.FindTimelineMarkers(['ActionA']) records = [ tir_module.TimelineInteractionRecord(e.name, e.start, e.end) for e in timeline_markers ] stats = rendering_stats.RenderingStats(renderer, browser, None, gpu, records) # Compare rendering stats to reference - Only drm flip stats should # count self.assertEquals(stats.frame_timestamps, ref_stats.frame_timestamps) self.assertEquals(stats.frame_times, ref_stats.frame_times)
def testBothSurfaceFlingerAndDisplayStats(self): timeline = model.TimelineModel() timer = MockTimer() ref_stats = ReferenceRenderingStats() ref_stats.AppendNewRange() surface_flinger = timeline.GetOrCreateProcess(pid=4) surface_flinger.name = 'SurfaceFlinger' surface_flinger_thread = surface_flinger.GetOrCreateThread(tid=41) renderer = timeline.GetOrCreateProcess(pid=2) browser = timeline.GetOrCreateProcess(pid=3) browser_main = browser.GetOrCreateThread(tid=31) browser_main.BeginSlice('webkit.console', 'ActionA', timer.AdvanceAndGet(2, 4), '') # Create SurfaceFlinger stats and display rendering stats. for i in xrange(0, 10): first = (i == 0) AddSurfaceFlingerStats(timer, surface_flinger_thread, first, ref_stats) timer.Advance(2, 4) for i in xrange(0, 10): first = (i == 0) AddDisplayRenderingStats(timer, browser_main, first, None) timer.Advance(5, 10) browser_main.EndSlice(timer.AdvanceAndGet()) timer.Advance(2, 4) browser.FinalizeImport() renderer.FinalizeImport() timeline_markers = timeline.FindTimelineMarkers(['ActionA']) timeline_ranges = [ bounds.Bounds.CreateFromEvent(marker) for marker in timeline_markers ] stats = rendering_stats.RenderingStats(renderer, browser, surface_flinger, timeline_ranges) # Compare rendering stats to reference - Only SurfaceFlinger stats should # count self.assertEquals(stats.frame_timestamps, ref_stats.frame_timestamps) self.assertEquals(stats.frame_times, ref_stats.frame_times)
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 testRangeWithoutFrames(self): timer = MockTimer() timeline = model.TimelineModel() # Create a renderer process, with a main thread and impl thread. renderer = timeline.GetOrCreateProcess(pid=2) renderer_main = renderer.GetOrCreateThread(tid=21) renderer_compositor = renderer.GetOrCreateThread(tid=22) # Create 10 main and impl rendering stats events for Action A. renderer_main.BeginSlice('webkit.console', 'ActionA', timer.AdvanceAndGet(2, 4), '') for i in xrange(0, 10): first = (i == 0) AddImplThreadRenderingStats(timer, renderer_compositor, first, None) renderer_main.EndSlice(timer.AdvanceAndGet(2, 4)) timer.Advance(2, 4) # Create 5 main and impl rendering stats events not within any action. for i in xrange(0, 5): first = (i == 0) AddImplThreadRenderingStats(timer, renderer_compositor, first, None) # Create Action B without any frames. This should trigger # NotEnoughFramesError when the RenderingStats object is created. renderer_main.BeginSlice('webkit.console', 'ActionB', timer.AdvanceAndGet(2, 4), '') renderer_main.EndSlice(timer.AdvanceAndGet(2, 4)) renderer.FinalizeImport() timeline_markers = timeline.FindTimelineMarkers(['ActionA', 'ActionB']) timeline_ranges = [ bounds.Bounds.CreateFromEvent(marker) for marker in timeline_markers ] stats = rendering_stats.RenderingStats(renderer, None, None, timeline_ranges) self.assertEquals(0, len(stats.frame_timestamps[1]))
def testFromTimeline(self): timeline = model.TimelineModel() # Create a browser process and a renderer process, and a main thread and # impl thread for each. browser = timeline.GetOrCreateProcess(pid=1) browser_main = browser.GetOrCreateThread(tid=11) renderer = timeline.GetOrCreateProcess(pid=2) timer = MockTimer() browser_ref_stats = ReferenceRenderingStats() browser_ref_stats.AppendNewRange() # Add display rendering stats. browser_main.BeginSlice('webkit.console', 'Action0', timer.AdvanceAndGet(2, 4), '') for i in xrange(0, 10): first = (i == 0) AddDisplayRenderingStats(timer, browser_main, first, browser_ref_stats) timer.Advance(5, 10) browser_main.EndSlice(timer.AdvanceAndGet(2, 4)) browser.FinalizeImport() timeline_markers = timeline.FindTimelineMarkers(['Action0']) records = [ tir_module.TimelineInteractionRecord(e.name, e.start, e.end) for e in timeline_markers ] stats = rendering_stats.RenderingStats(renderer, browser, None, records) # Compare rendering stats to reference. self.assertEquals(stats.frame_timestamps, browser_ref_stats.frame_timestamps) self.assertEquals(stats.frame_times, browser_ref_stats.frame_times)
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. renderer_main.BeginSlice('webkit.console', 'ActionA', timer.AdvanceAndGet(2, 4), '') for _ in xrange(0, 10): AddInputLatencyStats(timer, browser_main, renderer_main, ref_latency) renderer_main.EndSlice(timer.AdvanceAndGet(2, 4)) # 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. renderer_main.BeginSlice('webkit.console', 'ActionB', timer.AdvanceAndGet(2, 4), '') for _ in xrange(0, 10): AddInputLatencyStats(timer, browser_main, renderer_main, ref_latency) renderer_main.EndSlice(timer.AdvanceAndGet(2, 4)) # Create 10 input latency stats events for Action A. renderer_main.BeginSlice('webkit.console', 'ActionA', timer.AdvanceAndGet(2, 4), '') for _ in xrange(0, 10): AddInputLatencyStats(timer, browser_main, renderer_main, ref_latency) renderer_main.EndSlice(timer.AdvanceAndGet(2, 4)) browser.FinalizeImport() renderer.FinalizeImport() latency_events = [] timeline_markers = timeline.FindTimelineMarkers( ['ActionA', 'ActionB', 'ActionA']) timeline_ranges = [bounds.Bounds.CreateFromEvent(marker) for marker in timeline_markers] for timeline_range in timeline_ranges: if timeline_range.is_empty: continue latency_events.extend(rendering_stats.GetLatencyEvents( browser, timeline_range)) self.assertEquals(latency_events, ref_latency.input_event) event_latency_result = rendering_stats.ComputeEventLatencies(latency_events) self.assertEquals(event_latency_result, ref_latency.input_event_latency) stats = rendering_stats.RenderingStats( renderer, browser, None, timeline_ranges) self.assertEquals( perf_tests_helper.FlattenList(stats.input_event_latency), [latency for name, latency in ref_latency.input_event_latency if name != rendering_stats.SCROLL_UPDATE_EVENT_NAME]) self.assertEquals( perf_tests_helper.FlattenList(stats.scroll_update_latency), [latency for name, latency in ref_latency.input_event_latency if name == rendering_stats.SCROLL_UPDATE_EVENT_NAME]) self.assertEquals( perf_tests_helper.FlattenList(stats.gesture_scroll_update_latency), [latency for name, latency in ref_latency.input_event_latency if name == rendering_stats.GESTURE_SCROLL_UPDATE_EVENT_NAME])
def testFromTimeline(self): timeline = model.TimelineModel() # Create a browser process and a renderer process, and a main thread and # impl thread for each. browser = timeline.GetOrCreateProcess(pid=1) browser_compositor = browser.GetOrCreateThread(tid=12) renderer = timeline.GetOrCreateProcess(pid=2) renderer_main = renderer.GetOrCreateThread(tid=21) renderer_compositor = renderer.GetOrCreateThread(tid=22) timer = MockTimer() renderer_ref_stats = ReferenceRenderingStats() browser_ref_stats = ReferenceRenderingStats() # Create 10 main and impl rendering stats events for Action A. renderer_main.BeginSlice('webkit.console', 'ActionA', timer.AdvanceAndGet(2, 4), '') renderer_ref_stats.AppendNewRange() browser_ref_stats.AppendNewRange() for i in xrange(0, 10): first = (i == 0) AddImplThreadRenderingStats( timer, renderer_compositor, first, renderer_ref_stats) AddImplThreadRenderingStats( timer, browser_compositor, first, browser_ref_stats) renderer_main.EndSlice(timer.AdvanceAndGet(2, 4)) # Create 5 main and impl rendering stats events not within any action. for i in xrange(0, 5): first = (i == 0) AddImplThreadRenderingStats(timer, renderer_compositor, first, None) AddImplThreadRenderingStats(timer, browser_compositor, first, None) # Create 10 main and impl rendering stats events for Action B. renderer_main.BeginSlice('webkit.console', 'ActionB', timer.AdvanceAndGet(2, 4), '') renderer_ref_stats.AppendNewRange() browser_ref_stats.AppendNewRange() for i in xrange(0, 10): first = (i == 0) AddImplThreadRenderingStats( timer, renderer_compositor, first, renderer_ref_stats) AddImplThreadRenderingStats( timer, browser_compositor, first, browser_ref_stats) renderer_main.EndSlice(timer.AdvanceAndGet(2, 4)) # Create 10 main and impl rendering stats events for Action A. renderer_main.BeginSlice('webkit.console', 'ActionA', timer.AdvanceAndGet(2, 4), '') renderer_ref_stats.AppendNewRange() browser_ref_stats.AppendNewRange() for i in xrange(0, 10): first = (i == 0) AddImplThreadRenderingStats( timer, renderer_compositor, first, renderer_ref_stats) AddImplThreadRenderingStats( timer, browser_compositor, first, browser_ref_stats) renderer_main.EndSlice(timer.AdvanceAndGet(2, 4)) timer.Advance(2, 4) browser.FinalizeImport() renderer.FinalizeImport() timeline_markers = timeline.FindTimelineMarkers( ['ActionA', 'ActionB', 'ActionA']) timeline_ranges = [bounds.Bounds.CreateFromEvent(marker) for marker in timeline_markers] stats = rendering_stats.RenderingStats( renderer, browser, None, timeline_ranges) # Compare rendering stats to reference. self.assertEquals(stats.frame_timestamps, browser_ref_stats.frame_timestamps) self.assertEquals(stats.frame_times, browser_ref_stats.frame_times) self.assertEquals(stats.approximated_pixel_percentages, renderer_ref_stats.approximated_pixel_percentages) self.assertEquals(stats.checkerboarded_pixel_percentages, renderer_ref_stats.checkerboarded_pixel_percentages)
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.).'))