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