| Index: tools/perf/metrics/speedindex.py
|
| diff --git a/tools/perf/metrics/speedindex.py b/tools/perf/metrics/speedindex.py
|
| index 389cd7e0c767565ab1fe4c272d7a6ce31b8e0724..4ada00809ee1b14a6f16270998d97434d4f2c82e 100644
|
| --- a/tools/perf/metrics/speedindex.py
|
| +++ b/tools/perf/metrics/speedindex.py
|
| @@ -3,6 +3,7 @@
|
| # found in the LICENSE file.
|
|
|
| import collections
|
| +import logging
|
|
|
| from metrics import Metric
|
| from telemetry.image_processing import image_util
|
| @@ -36,12 +37,15 @@ class SpeedIndexMetric(Metric):
|
| a PageTest, so that all the events can be captured. If it's called
|
| in DidNavigateToPage, that will be too late.
|
| """
|
| - self._impl = (VideoSpeedIndexImpl() if tab.video_capture_supported else
|
| - PaintRectSpeedIndexImpl())
|
| + if not tab.video_capture_supported:
|
| + return
|
| + self._impl = VideoSpeedIndexImpl()
|
| self._impl.Start(tab)
|
|
|
| def Stop(self, _, tab):
|
| - """Stop timeline recording."""
|
| + """Stop recording."""
|
| + if not tab.video_capture_supported:
|
| + return
|
| assert self._impl, 'Must call Start() before Stop()'
|
| assert self.IsFinished(tab), 'Must wait for IsFinished() before Stop()'
|
| self._impl.Stop(tab)
|
| @@ -50,9 +54,16 @@ class SpeedIndexMetric(Metric):
|
| # pylint: disable=W0221
|
| def AddResults(self, tab, results, chart_name=None):
|
| """Calculate the speed index and add it to the results."""
|
| - index = self._impl.CalculateSpeedIndex(tab)
|
| - # Release the tab so that it can be disconnected.
|
| - self._impl = None
|
| + try:
|
| + if tab.video_capture_supported:
|
| + index = self._impl.CalculateSpeedIndex(tab)
|
| + none_value_reason = None
|
| + else:
|
| + index = None
|
| + none_value_reason = 'Video capture is not supported.'
|
| + finally:
|
| + self._impl = None # Release the tab so that it can be disconnected.
|
| +
|
| results.AddValue(scalar.ScalarValue(
|
| results.current_page, '%s_speed_index' % chart_name, 'ms', index,
|
| description='Speed Index. This focuses on time when visible parts of '
|
| @@ -70,15 +81,10 @@ class SpeedIndexMetric(Metric):
|
| 'there are two implementations: for Android and for '
|
| 'Desktop. The Android version uses video capture; the '
|
| 'Desktop one uses paint events and has extra overhead to '
|
| - 'catch paint events.'))
|
| + 'catch paint events.', none_value_reason=none_value_reason))
|
|
|
| def IsFinished(self, tab):
|
| - """Decide whether the timeline recording should be stopped.
|
| -
|
| - When the timeline recording is stopped determines which paint events
|
| - are used in the speed index metric calculation. In general, the recording
|
| - should continue if there has just been some data received, because
|
| - this suggests that painting may continue.
|
| + """Decide whether the recording should be stopped.
|
|
|
| A page may repeatedly request resources in an infinite loop; a timeout
|
| should be placed in any measurement that uses this metric, e.g.:
|
| @@ -182,145 +188,3 @@ class VideoSpeedIndexImpl(SpeedIndexImpl):
|
| def GetTimeCompletenessList(self, tab):
|
| assert self._time_completeness_list, 'Must call Stop() first.'
|
| return self._time_completeness_list
|
| -
|
| -
|
| -class PaintRectSpeedIndexImpl(SpeedIndexImpl):
|
| -
|
| - def __init__(self):
|
| - super(PaintRectSpeedIndexImpl, self).__init__()
|
| -
|
| - def Start(self, tab):
|
| - tab.StartTimelineRecording()
|
| -
|
| - def Stop(self, tab):
|
| - tab.StopTimelineRecording()
|
| -
|
| - def GetTimeCompletenessList(self, tab):
|
| - events = tab.timeline_model.GetAllEvents()
|
| - viewport = self._GetViewportSize(tab)
|
| - paint_events = self._IncludedPaintEvents(events)
|
| - time_area_dict = self._TimeAreaDict(paint_events, viewport)
|
| - total_area = sum(time_area_dict.values())
|
| - assert total_area > 0.0, 'Total paint event area must be greater than 0.'
|
| - completeness = 0.0
|
| - time_completeness_list = []
|
| -
|
| - # TODO(tonyg): This sets the start time to the start of the first paint
|
| - # event. That can't be correct. The start time should be navigationStart.
|
| - # Since the previous screen is not cleared at navigationStart, we should
|
| - # probably assume the completeness is 0 until the first paint and add the
|
| - # time of navigationStart as the start. We need to confirm what WPT does.
|
| - time_completeness_list.append(
|
| - (tab.timeline_model.GetAllEvents()[0].start, completeness))
|
| -
|
| - for time, area in sorted(time_area_dict.items()):
|
| - completeness += float(area) / total_area
|
| - # Visual progress is rounded to the nearest percentage point as in WPT.
|
| - time_completeness_list.append((time, round(completeness, 2)))
|
| - return time_completeness_list
|
| -
|
| - def _GetViewportSize(self, tab):
|
| - """Returns dimensions of the viewport."""
|
| - return tab.EvaluateJavaScript('[ window.innerWidth, window.innerHeight ]')
|
| -
|
| - def _IncludedPaintEvents(self, events):
|
| - """Get all events that are counted in the calculation of the speed index.
|
| -
|
| - There's one category of paint event that's filtered out: paint events
|
| - that occur before the first 'ResourceReceiveResponse' and 'Layout' events.
|
| -
|
| - Previously in the WPT speed index, paint events that contain children paint
|
| - events were also filtered out.
|
| - """
|
| - def FirstLayoutTime(events):
|
| - """Get the start time of the first layout after a resource received."""
|
| - has_received_response = False
|
| - for event in events:
|
| - if event.name == 'ResourceReceiveResponse':
|
| - has_received_response = True
|
| - elif has_received_response and event.name == 'Layout':
|
| - return event.start
|
| - assert False, 'There were no layout events after resource receive events.'
|
| -
|
| - first_layout_time = FirstLayoutTime(events)
|
| - paint_events = [e for e in events
|
| - if e.start >= first_layout_time and e.name == 'Paint']
|
| - return paint_events
|
| -
|
| - def _TimeAreaDict(self, paint_events, viewport):
|
| - """Make a dict from time to adjusted area value for events at that time.
|
| -
|
| - The adjusted area value of each paint event is determined by how many paint
|
| - events cover the same rectangle, and whether it's a full-window paint event.
|
| - "Adjusted area" can also be thought of as "points" of visual completeness --
|
| - each rectangle has a certain number of points and these points are
|
| - distributed amongst the paint events that paint that rectangle.
|
| -
|
| - Args:
|
| - paint_events: A list of paint events
|
| - viewport: A tuple (width, height) of the window.
|
| -
|
| - Returns:
|
| - A dictionary of times of each paint event (in milliseconds) to the
|
| - adjusted area that the paint event is worth.
|
| - """
|
| - width, height = viewport
|
| - fullscreen_area = width * height
|
| -
|
| - def ClippedArea(rectangle):
|
| - """Returns rectangle area clipped to viewport size."""
|
| - _, x0, y0, x1, y1 = rectangle
|
| - clipped_width = max(0, min(width, x1) - max(0, x0))
|
| - clipped_height = max(0, min(height, y1) - max(0, y0))
|
| - return clipped_width * clipped_height
|
| -
|
| - grouped = self._GroupEventByRectangle(paint_events)
|
| - event_area_dict = collections.defaultdict(int)
|
| -
|
| - for rectangle, events in grouped.items():
|
| - # The area points for each rectangle are divided up among the paint
|
| - # events in that rectangle.
|
| - area = ClippedArea(rectangle)
|
| - update_count = len(events)
|
| - adjusted_area = float(area) / update_count
|
| -
|
| - # Paint events for the largest-area rectangle are counted as 50%.
|
| - if area == fullscreen_area:
|
| - adjusted_area /= 2
|
| -
|
| - for event in events:
|
| - # The end time for an event is used for that event's time.
|
| - event_time = event.end
|
| - event_area_dict[event_time] += adjusted_area
|
| -
|
| - return event_area_dict
|
| -
|
| - def _GetRectangle(self, paint_event):
|
| - """Get the specific rectangle on the screen for a paint event.
|
| -
|
| - Each paint event belongs to a frame (as in html <frame> or <iframe>).
|
| - This, together with location and dimensions, comprises a rectangle.
|
| - In the WPT source, this 'rectangle' is also called a 'region'.
|
| - """
|
| - def GetBox(quad):
|
| - """Gets top-left and bottom-right coordinates from paint event.
|
| -
|
| - In the timeline data from devtools, paint rectangle dimensions are
|
| - represented x-y coordinates of four corners, clockwise from the top-left.
|
| - See: function WebInspector.TimelinePresentationModel.quadFromRectData
|
| - in file src/out/Debug/obj/gen/devtools/TimelinePanel.js.
|
| - """
|
| - x0, y0, _, _, x1, y1, _, _ = quad
|
| - return (x0, y0, x1, y1)
|
| -
|
| - assert paint_event.name == 'Paint'
|
| - frame = paint_event.args['frameId']
|
| - return (frame,) + GetBox(paint_event.args['data']['clip'])
|
| -
|
| - def _GroupEventByRectangle(self, paint_events):
|
| - """Group all paint events according to the rectangle that they update."""
|
| - result = collections.defaultdict(list)
|
| - for event in paint_events:
|
| - assert event.name == 'Paint'
|
| - result[self._GetRectangle(event)].append(event)
|
| - return result
|
|
|