| # 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. |
| |
| from collections import defaultdict |
| |
| from telemetry.timeline import bounds |
| from telemetry.timeline import slice as slice_module |
| |
| |
| class MissingData(Exception): |
| pass |
| |
| |
| class NoBeginFrameIdException(Exception): |
| pass |
| |
| |
| class RenderingFrame(object): |
| """Object with information about the triggering of a BeginMainFrame event.""" |
| send_begin_frame_event = 'ThreadProxy::ScheduledActionSendBeginMainFrame' |
| begin_main_frame_event = 'ThreadProxy::BeginMainFrame' |
| |
| def __init__(self, events): |
| all_send_begin_frame_events = [e for e in events |
| if e.name == self.send_begin_frame_event] |
| if len(all_send_begin_frame_events) != 1: |
| raise MissingData('There must be at exactly one %s event.' % |
| self.send_begin_frame_event) |
| |
| all_begin_main_frame_events = [e for e in events |
| if e.name == self.begin_main_frame_event] |
| if not all_begin_main_frame_events: |
| raise MissingData('There must be at least one %s event.' % |
| self.begin_main_frame_event) |
| all_begin_main_frame_events.sort(key=lambda e: e.start) |
| |
| self._send_begin_frame = all_send_begin_frame_events[0] |
| self._begin_main_frame = all_begin_main_frame_events[-1] |
| |
| self._bounds = bounds.Bounds() |
| self._bounds.AddEvent(self._begin_main_frame) |
| self._bounds.AddEvent(self._send_begin_frame) |
| |
| @staticmethod |
| def IsEventUseful(event): |
| return event.name in [RenderingFrame.send_begin_frame_event, |
| RenderingFrame.begin_main_frame_event] |
| |
| @property |
| def bounds(self): |
| return self._bounds |
| |
| @property |
| def queueing_duration(self): |
| return self._begin_main_frame.start - self._send_begin_frame.start |
| |
| |
| def GetFrameEventsInsideRange(renderer_process, timeline_range): |
| """Returns RenderingFrames for all relevant events in the timeline_range.""" |
| # First filter all events from the renderer_process and turn them into a |
| # dictonary of the form: |
| # {0: [send_begin_frame, begin_main_frame, begin_main_frame], |
| # 1: [begin_main_frame, send_begin_frame], |
| # 2: [send_begin_frame, begin_main_frame]} |
| begin_frame_events_by_id = defaultdict(list) |
| for event in renderer_process.IterAllEvents( |
| event_type_predicate=lambda t: t == slice_module.Slice, |
| event_predicate=RenderingFrame.IsEventUseful): |
| begin_frame_id = event.args.get('begin_frame_id', None) |
| if begin_frame_id is None: |
| raise NoBeginFrameIdException('Event is missing a begin_frame_id.') |
| begin_frame_events_by_id[begin_frame_id].append(event) |
| |
| # Now, create RenderingFrames for events wherever possible. |
| frames = [] |
| for events in begin_frame_events_by_id.values(): |
| try: |
| frame = RenderingFrame(events) |
| if frame.bounds.Intersects(timeline_range): |
| frames.append(frame) |
| except MissingData: |
| continue |
| frames.sort(key=lambda frame: frame.bounds.min) |
| |
| return frames |