• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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'''A container for timeline-based events and traces and can handle importing
5raw event data from different sources. This model closely resembles that in the
6trace_viewer project:
7https://code.google.com/p/trace-viewer/
8'''
9
10from operator import attrgetter
11
12import telemetry.timeline.process as tracing_process
13
14# Register importers for data
15from telemetry.timeline import bounds
16from telemetry.timeline import empty_timeline_data_importer
17from telemetry.timeline import inspector_importer
18from telemetry.timeline import trace_event_importer
19
20_IMPORTERS = [
21    empty_timeline_data_importer.EmptyTimelineDataImporter,
22    inspector_importer.InspectorTimelineImporter,
23    trace_event_importer.TraceEventTimelineImporter
24]
25
26
27class MarkerMismatchError(Exception):
28  def __init__(self):
29    super(MarkerMismatchError, self).__init__(
30        'Number or order of timeline markers does not match provided labels')
31
32
33class MarkerOverlapError(Exception):
34  def __init__(self):
35    super(MarkerOverlapError, self).__init__(
36        'Overlapping timeline markers found')
37
38
39class TimelineModel(object):
40  def __init__(self, timeline_data=None, shift_world_to_zero=True):
41    """ Initializes a TimelineModel. timeline_data can be a single TimelineData
42    object, a list of TimelineData objects, or None. If timeline_data is not
43    None, all events from it will be imported into the model. The events will
44    be shifted such that the first event starts at time 0, if
45    shift_world_to_zero is True.
46    """
47    self._bounds = bounds.Bounds()
48    self._thread_time_bounds = {}
49    self._processes = {}
50    self._browser_process = None
51    self._frozen = False
52    self._tab_ids_to_renderer_threads_map = {}
53    self.import_errors = []
54    self.metadata = []
55    self.flow_events = []
56    if timeline_data is not None:
57      self.ImportTraces(timeline_data, shift_world_to_zero=shift_world_to_zero)
58
59  @property
60  def bounds(self):
61    return self._bounds
62
63  @property
64  def thread_time_bounds(self):
65    return self._thread_time_bounds
66
67  @property
68  def processes(self):
69    return self._processes
70
71  @property
72  #pylint: disable=E0202
73  def browser_process(self):
74    return self._browser_process
75
76  @browser_process.setter
77  #pylint: disable=E0202
78  def browser_process(self, browser_process):
79    self._browser_process = browser_process
80
81  def AddMappingFromTabIdToRendererThread(self, tab_id, renderer_thread):
82    if self._frozen:
83      raise Exception('Cannot add mapping from tab id to renderer thread once '
84                      'trace is imported')
85    self._tab_ids_to_renderer_threads_map[tab_id] = renderer_thread
86
87  def ImportTraces(self, timeline_data, shift_world_to_zero=True):
88    if self._frozen:
89      raise Exception("Cannot add events once trace is imported")
90
91    importers = []
92    if isinstance(timeline_data, list):
93      for item in timeline_data:
94        importers.append(self._CreateImporter(item))
95    else:
96      importers.append(self._CreateImporter(timeline_data))
97
98    importers.sort(cmp=lambda x, y: x.import_priority - y.import_priority)
99
100    for importer in importers:
101      # TODO: catch exceptions here and add it to error list
102      importer.ImportEvents()
103    self.FinalizeImport(shift_world_to_zero, importers)
104
105  def FinalizeImport(self, shift_world_to_zero=False, importers=None):
106    if importers == None:
107      importers = []
108    self.UpdateBounds()
109    if not self.bounds.is_empty:
110      for process in self._processes.itervalues():
111        process.AutoCloseOpenSlices(self.bounds.max,
112                                    self.thread_time_bounds)
113
114    for importer in importers:
115      importer.FinalizeImport()
116
117    for process in self.processes.itervalues():
118      process.FinalizeImport()
119
120    if shift_world_to_zero:
121      self.ShiftWorldToZero()
122    self.UpdateBounds()
123
124    # Because of FinalizeImport, it would probably be a good idea
125    # to prevent the timeline from from being modified.
126    self._frozen = True
127
128  def ShiftWorldToZero(self):
129    self.UpdateBounds()
130    if self._bounds.is_empty:
131      return
132    shift_amount = self._bounds.min
133    for event in self.IterAllEvents():
134      event.start -= shift_amount
135
136  def UpdateBounds(self):
137    self._bounds.Reset()
138    for event in self.IterAllEvents():
139      self._bounds.AddValue(event.start)
140      self._bounds.AddValue(event.end)
141
142    self._thread_time_bounds = {}
143    for thread in self.GetAllThreads():
144      self._thread_time_bounds[thread] = bounds.Bounds()
145      for event in thread.IterEventsInThisContainer():
146        if event.thread_start != None:
147          self._thread_time_bounds[thread].AddValue(event.thread_start)
148        if event.thread_end != None:
149          self._thread_time_bounds[thread].AddValue(event.thread_end)
150
151  def GetAllContainers(self):
152    containers = []
153    def Iter(container):
154      containers.append(container)
155      for container in container.IterChildContainers():
156        Iter(container)
157    for process in self._processes.itervalues():
158      Iter(process)
159    return containers
160
161  def IterAllEvents(self):
162    for container in self.GetAllContainers():
163      for event in container.IterEventsInThisContainer():
164        yield event
165
166  def GetAllProcesses(self):
167    return self._processes.values()
168
169  def GetAllThreads(self):
170    threads = []
171    for process in self._processes.values():
172      threads.extend(process.threads.values())
173    return threads
174
175  def GetAllEvents(self):
176    return list(self.IterAllEvents())
177
178  def GetAllEventsOfName(self, name, only_root_events=False):
179    events = [e for e in self.IterAllEvents() if e.name == name]
180    if only_root_events:
181      return filter(lambda ev: ev.parent_slice == None, events)
182    else:
183      return events
184
185  def GetEventOfName(self, name, only_root_events=False,
186                     fail_if_more_than_one=False):
187    events = self.GetAllEventsOfName(name, only_root_events)
188    if len(events) == 0:
189      raise Exception('No event of name "%s" found.' % name)
190    if fail_if_more_than_one and len(events) > 1:
191      raise Exception('More than one event of name "%s" found.' % name)
192    return events[0]
193
194  def GetOrCreateProcess(self, pid):
195    if pid not in self._processes:
196      assert not self._frozen
197      self._processes[pid] = tracing_process.Process(self, pid)
198    return self._processes[pid]
199
200  def FindTimelineMarkers(self, timeline_marker_names):
201    """Find the timeline events with the given names.
202
203    If the number and order of events found does not match the names,
204    raise an error.
205    """
206    # Make sure names are in a list and remove all None names
207    if not isinstance(timeline_marker_names, list):
208      timeline_marker_names = [timeline_marker_names]
209    names = [x for x in timeline_marker_names if x is not None]
210
211    # Gather all events that match the names and sort them.
212    events = []
213    name_set = set()
214    for name in names:
215      name_set.add(name)
216    for name in name_set:
217      events.extend(self.GetAllEventsOfName(name, True))
218    events.sort(key=attrgetter('start'))
219
220    # Check if the number and order of events matches the provided names,
221    # and that the events don't overlap.
222    if len(events) != len(names):
223      raise MarkerMismatchError()
224    for (i, event) in enumerate(events):
225      if event.name != names[i]:
226        raise MarkerMismatchError()
227    for i in xrange(0, len(events)):
228      for j in xrange(i+1, len(events)):
229        if (events[j].start < events[i].start + events[i].duration):
230          raise MarkerOverlapError()
231
232    return events
233
234  def GetRendererProcessFromTabId(self, tab_id):
235    renderer_thread = self.GetRendererThreadFromTabId(tab_id)
236    if renderer_thread:
237      return renderer_thread.parent
238    return None
239
240  def GetRendererThreadFromTabId(self, tab_id):
241    return self._tab_ids_to_renderer_threads_map.get(tab_id, None)
242
243  def _CreateImporter(self, event_data):
244    for importer_class in _IMPORTERS:
245      if importer_class.CanImport(event_data):
246        return importer_class(self, event_data)
247    raise ValueError("Could not find an importer for the provided event data")
248