| Index: tools/telemetry/telemetry/web_perf/metrics/rendering_stats.py
|
| diff --git a/tools/telemetry/telemetry/web_perf/metrics/rendering_stats.py b/tools/telemetry/telemetry/web_perf/metrics/rendering_stats.py
|
| deleted file mode 100644
|
| index 65bdbee87c5582f7bd91403b20b81548786d5585..0000000000000000000000000000000000000000
|
| --- a/tools/telemetry/telemetry/web_perf/metrics/rendering_stats.py
|
| +++ /dev/null
|
| @@ -1,296 +0,0 @@
|
| -# Copyright 2014 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.
|
| -import itertools
|
| -
|
| -from operator import attrgetter
|
| -
|
| -from telemetry.web_perf.metrics import rendering_frame
|
| -
|
| -# These are LatencyInfo component names indicating the various components
|
| -# that the input event has travelled through.
|
| -# This is when the input event first reaches chrome.
|
| -UI_COMP_NAME = 'INPUT_EVENT_LATENCY_UI_COMPONENT'
|
| -# This is when the input event was originally created by OS.
|
| -ORIGINAL_COMP_NAME = 'INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT'
|
| -# This is when the input event was sent from browser to renderer.
|
| -BEGIN_COMP_NAME = 'INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT'
|
| -# This is when an input event is turned into a scroll update.
|
| -BEGIN_SCROLL_UPDATE_COMP_NAME = (
|
| - 'LATENCY_BEGIN_SCROLL_LISTENER_UPDATE_MAIN_COMPONENT')
|
| -# This is when a scroll update is forwarded to the main thread.
|
| -FORWARD_SCROLL_UPDATE_COMP_NAME = (
|
| - 'INPUT_EVENT_LATENCY_FORWARD_SCROLL_UPDATE_TO_MAIN_COMPONENT')
|
| -# This is when the input event has reached swap buffer.
|
| -END_COMP_NAME = 'INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT'
|
| -
|
| -# Name for a main thread scroll update latency event.
|
| -MAIN_THREAD_SCROLL_UPDATE_EVENT_NAME = 'Latency::ScrollUpdate'
|
| -# Name for a gesture scroll update latency event.
|
| -GESTURE_SCROLL_UPDATE_EVENT_NAME = 'InputLatency::GestureScrollUpdate'
|
| -
|
| -# These are keys used in the 'data' field dictionary located in
|
| -# BenchmarkInstrumentation::ImplThreadRenderingStats.
|
| -VISIBLE_CONTENT_DATA = 'visible_content_area'
|
| -APPROXIMATED_VISIBLE_CONTENT_DATA = 'approximated_visible_content_area'
|
| -CHECKERBOARDED_VISIBLE_CONTENT_DATA = 'checkerboarded_visible_content_area'
|
| -# These are keys used in the 'errors' field dictionary located in
|
| -# RenderingStats in this file.
|
| -APPROXIMATED_PIXEL_ERROR = 'approximated_pixel_percentages'
|
| -CHECKERBOARDED_PIXEL_ERROR = 'checkerboarded_pixel_percentages'
|
| -
|
| -
|
| -def GetLatencyEvents(process, timeline_range):
|
| - """Get LatencyInfo trace events from the process's trace buffer that are
|
| - within the timeline_range.
|
| -
|
| - Input events dump their LatencyInfo into trace buffer as async trace event
|
| - of name starting with "InputLatency". Non-input events with name starting
|
| - with "Latency". The trace event has a member 'data' containing its latency
|
| - history.
|
| -
|
| - """
|
| - latency_events = []
|
| - if not process:
|
| - return latency_events
|
| - for event in itertools.chain(
|
| - process.IterAllAsyncSlicesStartsWithName('InputLatency'),
|
| - process.IterAllAsyncSlicesStartsWithName('Latency')):
|
| - if event.start >= timeline_range.min and event.end <= timeline_range.max:
|
| - for ss in event.sub_slices:
|
| - if 'data' in ss.args:
|
| - latency_events.append(ss)
|
| - return latency_events
|
| -
|
| -
|
| -def ComputeEventLatencies(input_events):
|
| - """ Compute input event latencies.
|
| -
|
| - Input event latency is the time from when the input event is created to
|
| - when its resulted page is swap buffered.
|
| - Input event on different platforms uses different LatencyInfo component to
|
| - record its creation timestamp. We go through the following component list
|
| - to find the creation timestamp:
|
| - 1. INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT -- when event is created in OS
|
| - 2. INPUT_EVENT_LATENCY_UI_COMPONENT -- when event reaches Chrome
|
| - 3. INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT -- when event reaches RenderWidget
|
| -
|
| - If the latency starts with a
|
| - LATENCY_BEGIN_SCROLL_UPDATE_MAIN_COMPONENT component, then it is
|
| - classified as a scroll update instead of a normal input latency measure.
|
| -
|
| - Returns:
|
| - A list sorted by increasing start time of latencies which are tuples of
|
| - (input_event_name, latency_in_ms).
|
| - """
|
| - input_event_latencies = []
|
| - for event in input_events:
|
| - data = event.args['data']
|
| - if END_COMP_NAME in data:
|
| - end_time = data[END_COMP_NAME]['time']
|
| - if ORIGINAL_COMP_NAME in data:
|
| - start_time = data[ORIGINAL_COMP_NAME]['time']
|
| - elif UI_COMP_NAME in data:
|
| - start_time = data[UI_COMP_NAME]['time']
|
| - elif BEGIN_COMP_NAME in data:
|
| - start_time = data[BEGIN_COMP_NAME]['time']
|
| - elif BEGIN_SCROLL_UPDATE_COMP_NAME in data:
|
| - start_time = data[BEGIN_SCROLL_UPDATE_COMP_NAME]['time']
|
| - else:
|
| - raise ValueError('LatencyInfo has no begin component')
|
| - latency = (end_time - start_time) / 1000.0
|
| - input_event_latencies.append((start_time, event.name, latency))
|
| -
|
| - input_event_latencies.sort()
|
| - return [(name, latency) for _, name, latency in input_event_latencies]
|
| -
|
| -
|
| -def HasRenderingStats(process):
|
| - """ Returns True if the process contains at least one
|
| - BenchmarkInstrumentation::*RenderingStats event with a frame.
|
| - """
|
| - if not process:
|
| - return False
|
| - for event in process.IterAllSlicesOfName(
|
| - 'BenchmarkInstrumentation::DisplayRenderingStats'):
|
| - if 'data' in event.args and event.args['data']['frame_count'] == 1:
|
| - return True
|
| - for event in process.IterAllSlicesOfName(
|
| - 'BenchmarkInstrumentation::ImplThreadRenderingStats'):
|
| - if 'data' in event.args and event.args['data']['frame_count'] == 1:
|
| - return True
|
| - return False
|
| -
|
| -def GetTimestampEventName(process):
|
| - """ Returns the name of the events used to count frame timestamps. """
|
| - if process.name == 'SurfaceFlinger':
|
| - return 'vsync_before'
|
| -
|
| - event_name = 'BenchmarkInstrumentation::DisplayRenderingStats'
|
| - for event in process.IterAllSlicesOfName(event_name):
|
| - if 'data' in event.args and event.args['data']['frame_count'] == 1:
|
| - return event_name
|
| -
|
| - return 'BenchmarkInstrumentation::ImplThreadRenderingStats'
|
| -
|
| -class RenderingStats(object):
|
| - def __init__(self, renderer_process, browser_process, surface_flinger_process,
|
| - timeline_ranges):
|
| - """
|
| - Utility class for extracting rendering statistics from the timeline (or
|
| - other loggin facilities), and providing them in a common format to classes
|
| - that compute benchmark metrics from this data.
|
| -
|
| - Stats are lists of lists of numbers. The outer list stores one list per
|
| - timeline range.
|
| -
|
| - All *_time values are measured in milliseconds.
|
| - """
|
| - assert len(timeline_ranges) > 0
|
| - self.refresh_period = None
|
| -
|
| - # Find the top level process with rendering stats (browser or renderer).
|
| - if surface_flinger_process:
|
| - timestamp_process = surface_flinger_process
|
| - self._GetRefreshPeriodFromSurfaceFlingerProcess(surface_flinger_process)
|
| - elif HasRenderingStats(browser_process):
|
| - timestamp_process = browser_process
|
| - else:
|
| - timestamp_process = renderer_process
|
| -
|
| - timestamp_event_name = GetTimestampEventName(timestamp_process)
|
| -
|
| - # A lookup from list names below to any errors or exceptions encountered
|
| - # in attempting to generate that list.
|
| - self.errors = {}
|
| -
|
| - self.frame_timestamps = []
|
| - self.frame_times = []
|
| - self.approximated_pixel_percentages = []
|
| - self.checkerboarded_pixel_percentages = []
|
| - # End-to-end latency for input event - from when input event is
|
| - # generated to when the its resulted page is swap buffered.
|
| - self.input_event_latency = []
|
| - self.frame_queueing_durations = []
|
| - # Latency from when a scroll update is sent to the main thread until the
|
| - # resulting frame is swapped.
|
| - self.main_thread_scroll_latency = []
|
| - # Latency for a GestureScrollUpdate input event.
|
| - self.gesture_scroll_update_latency = []
|
| -
|
| - for timeline_range in timeline_ranges:
|
| - self.frame_timestamps.append([])
|
| - self.frame_times.append([])
|
| - self.approximated_pixel_percentages.append([])
|
| - self.checkerboarded_pixel_percentages.append([])
|
| - self.input_event_latency.append([])
|
| - self.main_thread_scroll_latency.append([])
|
| - self.gesture_scroll_update_latency.append([])
|
| -
|
| - if timeline_range.is_empty:
|
| - continue
|
| - self._InitFrameTimestampsFromTimeline(
|
| - timestamp_process, timestamp_event_name, timeline_range)
|
| - self._InitImplThreadRenderingStatsFromTimeline(
|
| - renderer_process, timeline_range)
|
| - self._InitInputLatencyStatsFromTimeline(
|
| - browser_process, renderer_process, timeline_range)
|
| - self._InitFrameQueueingDurationsFromTimeline(
|
| - renderer_process, timeline_range)
|
| -
|
| - def _GetRefreshPeriodFromSurfaceFlingerProcess(self, surface_flinger_process):
|
| - for event in surface_flinger_process.IterAllEventsOfName('vsync_before'):
|
| - self.refresh_period = event.args['data']['refresh_period']
|
| - return
|
| -
|
| - def _InitInputLatencyStatsFromTimeline(
|
| - self, browser_process, renderer_process, timeline_range):
|
| - latency_events = GetLatencyEvents(browser_process, timeline_range)
|
| - # Plugin input event's latency slice is generated in renderer process.
|
| - latency_events.extend(GetLatencyEvents(renderer_process, timeline_range))
|
| - event_latencies = ComputeEventLatencies(latency_events)
|
| - # Don't include scroll updates in the overall input latency measurement,
|
| - # because scroll updates can take much more time to process than other
|
| - # input events and would therefore add noise to overall latency numbers.
|
| - self.input_event_latency[-1] = [
|
| - latency for name, latency in event_latencies
|
| - if name != MAIN_THREAD_SCROLL_UPDATE_EVENT_NAME]
|
| - self.main_thread_scroll_latency[-1] = [
|
| - latency for name, latency in event_latencies
|
| - if name == MAIN_THREAD_SCROLL_UPDATE_EVENT_NAME]
|
| - self.gesture_scroll_update_latency[-1] = [
|
| - latency for name, latency in event_latencies
|
| - if name == GESTURE_SCROLL_UPDATE_EVENT_NAME]
|
| -
|
| - def _GatherEvents(self, event_name, process, timeline_range):
|
| - events = []
|
| - for event in process.IterAllSlicesOfName(event_name):
|
| - if event.start >= timeline_range.min and event.end <= timeline_range.max:
|
| - if 'data' not in event.args:
|
| - continue
|
| - events.append(event)
|
| - events.sort(key=attrgetter('start'))
|
| - return events
|
| -
|
| - def _AddFrameTimestamp(self, event):
|
| - frame_count = event.args['data']['frame_count']
|
| - if frame_count > 1:
|
| - raise ValueError('trace contains multi-frame render stats')
|
| - if frame_count == 1:
|
| - self.frame_timestamps[-1].append(
|
| - event.start)
|
| - if len(self.frame_timestamps[-1]) >= 2:
|
| - self.frame_times[-1].append(
|
| - self.frame_timestamps[-1][-1] - self.frame_timestamps[-1][-2])
|
| -
|
| - def _InitFrameTimestampsFromTimeline(
|
| - self, process, timestamp_event_name, timeline_range):
|
| - for event in self._GatherEvents(
|
| - timestamp_event_name, process, timeline_range):
|
| - self._AddFrameTimestamp(event)
|
| -
|
| - def _InitImplThreadRenderingStatsFromTimeline(self, process, timeline_range):
|
| - event_name = 'BenchmarkInstrumentation::ImplThreadRenderingStats'
|
| - for event in self._GatherEvents(event_name, process, timeline_range):
|
| - data = event.args['data']
|
| - if VISIBLE_CONTENT_DATA not in data:
|
| - self.errors[APPROXIMATED_PIXEL_ERROR] = (
|
| - 'Calculating approximated_pixel_percentages not possible because '
|
| - 'visible_content_area was missing.')
|
| - self.errors[CHECKERBOARDED_PIXEL_ERROR] = (
|
| - 'Calculating checkerboarded_pixel_percentages not possible because '
|
| - 'visible_content_area was missing.')
|
| - return
|
| - visible_content_area = data[VISIBLE_CONTENT_DATA]
|
| - if visible_content_area == 0:
|
| - self.errors[APPROXIMATED_PIXEL_ERROR] = (
|
| - 'Calculating approximated_pixel_percentages would have caused '
|
| - 'a divide-by-zero')
|
| - self.errors[CHECKERBOARDED_PIXEL_ERROR] = (
|
| - 'Calculating checkerboarded_pixel_percentages would have caused '
|
| - 'a divide-by-zero')
|
| - return
|
| - if APPROXIMATED_VISIBLE_CONTENT_DATA in data:
|
| - self.approximated_pixel_percentages[-1].append(
|
| - round(float(data[APPROXIMATED_VISIBLE_CONTENT_DATA]) /
|
| - float(data[VISIBLE_CONTENT_DATA]) * 100.0, 3))
|
| - else:
|
| - self.errors[APPROXIMATED_PIXEL_ERROR] = (
|
| - 'approximated_pixel_percentages was not recorded')
|
| - if CHECKERBOARDED_VISIBLE_CONTENT_DATA in data:
|
| - self.checkerboarded_pixel_percentages[-1].append(
|
| - round(float(data[CHECKERBOARDED_VISIBLE_CONTENT_DATA]) /
|
| - float(data[VISIBLE_CONTENT_DATA]) * 100.0, 3))
|
| - else:
|
| - self.errors[CHECKERBOARDED_PIXEL_ERROR] = (
|
| - 'checkerboarded_pixel_percentages was not recorded')
|
| -
|
| - def _InitFrameQueueingDurationsFromTimeline(self, process, timeline_range):
|
| - try:
|
| - events = rendering_frame.GetFrameEventsInsideRange(process,
|
| - timeline_range)
|
| - new_frame_queueing_durations = [e.queueing_duration for e in events]
|
| - self.frame_queueing_durations.append(new_frame_queueing_durations)
|
| - except rendering_frame.NoBeginFrameIdException:
|
| - self.errors['frame_queueing_durations'] = (
|
| - 'Current chrome version does not support the queueing delay metric.')
|
|
|