1# Copyright 2014 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5from collections import defaultdict 6 7from telemetry.timeline import bounds 8from telemetry.timeline import slice as slice_module 9 10 11class MissingData(Exception): 12 pass 13 14 15class NoBeginFrameIdException(Exception): 16 pass 17 18 19class RenderingFrame(object): 20 """Object with information about the triggering of a BeginMainFrame event.""" 21 send_begin_frame_event = 'ThreadProxy::ScheduledActionSendBeginMainFrame' 22 begin_main_frame_event = 'ThreadProxy::BeginMainFrame' 23 24 def __init__(self, events): 25 all_send_begin_frame_events = [e for e in events 26 if e.name == self.send_begin_frame_event] 27 if len(all_send_begin_frame_events) != 1: 28 raise MissingData('There must be at exactly one %s event.' % 29 self.send_begin_frame_event) 30 31 all_begin_main_frame_events = [e for e in events 32 if e.name == self.begin_main_frame_event] 33 if not all_begin_main_frame_events: 34 raise MissingData('There must be at least one %s event.' % 35 self.begin_main_frame_event) 36 all_begin_main_frame_events.sort(key=lambda e: e.start) 37 38 self._send_begin_frame = all_send_begin_frame_events[0] 39 self._begin_main_frame = all_begin_main_frame_events[-1] 40 41 self._bounds = bounds.Bounds() 42 self._bounds.AddEvent(self._begin_main_frame) 43 self._bounds.AddEvent(self._send_begin_frame) 44 45 @staticmethod 46 def IsEventUseful(event): 47 return event.name in [RenderingFrame.send_begin_frame_event, 48 RenderingFrame.begin_main_frame_event] 49 50 @property 51 def bounds(self): 52 return self._bounds 53 54 @property 55 def queueing_duration(self): 56 return self._begin_main_frame.start - self._send_begin_frame.start 57 58 59def GetFrameEventsInsideRange(renderer_process, timeline_range): 60 """Returns RenderingFrames for all relevant events in the timeline_range.""" 61 # First filter all events from the renderer_process and turn them into a 62 # dictonary of the form: 63 # {0: [send_begin_frame, begin_main_frame, begin_main_frame], 64 # 1: [begin_main_frame, send_begin_frame], 65 # 2: [send_begin_frame, begin_main_frame]} 66 begin_frame_events_by_id = defaultdict(list) 67 for event in renderer_process.IterAllEvents( 68 event_type_predicate=lambda t: t == slice_module.Slice, 69 event_predicate=RenderingFrame.IsEventUseful): 70 begin_frame_id = event.args.get('begin_frame_id', None) 71 if begin_frame_id is None: 72 raise NoBeginFrameIdException('Event is missing a begin_frame_id.') 73 begin_frame_events_by_id[begin_frame_id].append(event) 74 75 # Now, create RenderingFrames for events wherever possible. 76 frames = [] 77 for events in begin_frame_events_by_id.values(): 78 try: 79 frame = RenderingFrame(events) 80 if frame.bounds.Intersects(timeline_range): 81 frames.append(frame) 82 except MissingData: 83 continue 84 frames.sort(key=lambda frame: frame.bounds.min) 85 86 return frames 87