Exemplo n.º 1
0
    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)
Exemplo n.º 2
0
    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]))
Exemplo n.º 3
0
 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)
Exemplo n.º 5
0
 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)
Exemplo n.º 6
0
 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)
Exemplo n.º 10
0
    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]))
Exemplo n.º 12
0
    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)
Exemplo n.º 13
0
  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])
Exemplo n.º 14
0
  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)
Exemplo n.º 15
0
    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.).'))