• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2013 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
11import weakref
12
13import telemetry.core.timeline.process as tracing_process
14from telemetry.core import web_contents
15from telemetry.core import browser
16
17# Register importers for data
18from telemetry.core.timeline import inspector_importer
19from telemetry.core.timeline import bounds
20from telemetry.core.timeline import trace_event_importer
21
22_IMPORTERS = [
23    inspector_importer.InspectorTimelineImporter,
24    trace_event_importer.TraceEventTimelineImporter
25]
26
27
28class MarkerMismatchError(Exception):
29  def __init__(self):
30    super(MarkerMismatchError, self).__init__(
31        'Number or order of timeline markers does not match provided labels')
32
33
34class MarkerOverlapError(Exception):
35  def __init__(self):
36    super(MarkerOverlapError, self).__init__(
37        'Overlapping timeline markers found')
38
39
40class TimelineModel(object):
41  def __init__(self, event_data=None, shift_world_to_zero=True):
42    self._bounds = bounds.Bounds()
43    self._thread_time_bounds = bounds.Bounds()
44    self._processes = {}
45    self._frozen = False
46    self.import_errors = []
47    self.metadata = []
48    # Use a WeakKeyDictionary, because an ordinary dictionary could keep
49    # references to Tab objects around until it gets garbage collected.
50    # This would prevent telemetry from navigating to another page.
51    self._core_object_to_timeline_container_map = weakref.WeakKeyDictionary()
52
53    if event_data is not None:
54      self.ImportTraces([event_data], shift_world_to_zero=shift_world_to_zero)
55
56  @property
57  def bounds(self):
58    return self._bounds
59
60  @property
61  def thread_time_bounds(self):
62    return self._thread_time_bounds
63
64  @property
65  def processes(self):
66    return self._processes
67
68  def ImportTraces(self, traces, shift_world_to_zero=True):
69    if self._frozen:
70      raise Exception("Cannot add events once recording is done")
71
72    importers = []
73    for event_data in traces:
74      importers.append(self._CreateImporter(event_data))
75
76    importers.sort(cmp=lambda x, y: x.import_priority - y.import_priority)
77
78    for importer in importers:
79      # TODO: catch exceptions here and add it to error list
80      importer.ImportEvents()
81
82    self.UpdateBounds()
83    if not self.bounds.is_empty:
84      for process in self._processes.itervalues():
85        process.AutoCloseOpenSlices(self.bounds.max,
86                                    self.thread_time_bounds.max)
87
88    for importer in importers:
89      importer.FinalizeImport()
90
91    for process in self.processes.itervalues():
92      process.FinalizeImport()
93
94    if shift_world_to_zero:
95      self.ShiftWorldToZero()
96    self.UpdateBounds()
97
98    # Because of FinalizeImport, it would probably be a good idea
99    # to prevent the timeline from from being modified.
100    self._frozen = True
101
102  def ShiftWorldToZero(self):
103    self.UpdateBounds()
104    if self._bounds.is_empty:
105      return
106    shift_amount = self._bounds.min
107    thread_shift_amount = self._thread_time_bounds.min
108    for event in self.IterAllEvents():
109      event.start -= shift_amount
110      if event.thread_start != None:
111        event.thread_start -= thread_shift_amount
112
113  def UpdateBounds(self):
114    self._bounds.Reset()
115    self._thread_time_bounds.Reset()
116    for event in self.IterAllEvents():
117      self._bounds.AddValue(event.start)
118      self._bounds.AddValue(event.end)
119      if event.thread_start != None:
120        self._thread_time_bounds.AddValue(event.thread_start)
121      if event.thread_end != None:
122        self._thread_time_bounds.AddValue(event.thread_end)
123
124  def GetAllContainers(self):
125    containers = []
126    def Iter(container):
127      containers.append(container)
128      for container in container.IterChildContainers():
129        Iter(container)
130    for process in self._processes.itervalues():
131      Iter(process)
132    return containers
133
134  def IterAllEvents(self):
135    for container in self.GetAllContainers():
136      for event in container.IterEventsInThisContainer():
137        yield event
138
139  def GetAllProcesses(self):
140    return self._processes.values()
141
142  def GetAllThreads(self):
143    threads = []
144    for process in self._processes.values():
145      threads.extend(process.threads.values())
146    return threads
147
148  def GetAllEvents(self):
149    return list(self.IterAllEvents())
150
151  def GetAllEventsOfName(self, name):
152    return [e for e in self.IterAllEvents() if e.name == name]
153
154  def GetOrCreateProcess(self, pid):
155    if pid not in self._processes:
156      assert not self._frozen
157      self._processes[pid] = tracing_process.Process(self, pid)
158    return self._processes[pid]
159
160  def FindTimelineMarkers(self, timeline_marker_names):
161    """Find the timeline events with the given names.
162
163    If the number and order of events found does not match the names,
164    raise an error.
165    """
166    # Make sure names are in a list and remove all None names
167    if not isinstance(timeline_marker_names, list):
168      timeline_marker_names = [timeline_marker_names]
169    names = [x for x in timeline_marker_names if x is not None]
170
171    # Gather all events that match the names and sort them.
172    events = []
173    name_set = set()
174    for name in names:
175      name_set.add(name)
176    for name in name_set:
177      events.extend([s for s in self.GetAllEventsOfName(name)
178                     if s.parent_slice == None])
179    events.sort(key=attrgetter('start'))
180
181    # Check if the number and order of events matches the provided names,
182    # and that the events don't overlap.
183    if len(events) != len(names):
184      raise MarkerMismatchError()
185    for (i, event) in enumerate(events):
186      if event.name != names[i]:
187        raise MarkerMismatchError()
188    for i in xrange(0, len(events)):
189      for j in xrange(i+1, len(events)):
190        if (events[j].start < events[i].start + events[i].duration):
191          raise MarkerOverlapError()
192
193    return events
194
195  def GetRendererProcessFromTab(self, tab):
196    return self._core_object_to_timeline_container_map[tab]
197
198  def AddCoreObjectToContainerMapping(self, core_object, container):
199    """ Add a mapping from a core object to a timeline container.
200
201    Used for example to map a Tab to its renderer process in the timeline model.
202    """
203    assert(isinstance(core_object, web_contents.WebContents) or
204           isinstance(core_object, browser.Browser))
205    self._core_object_to_timeline_container_map[core_object] = container
206
207  def _CreateImporter(self, event_data):
208    for importer_class in _IMPORTERS:
209      if importer_class.CanImport(event_data):
210        return importer_class(self, event_data)
211    raise ValueError("Could not find an importer for the provided event data")
212