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
5 raw event data from different sources. This model closely resembles that in the
7 https://code.google.com/p/trace-viewer/
10 from operator import attrgetter
12 import telemetry.timeline.process as process_module
13 from telemetry.timeline import async_slice as async_slice_module
14 from telemetry.timeline import slice as slice_module
15 from telemetry.timeline import bounds
16 from telemetry.timeline import empty_timeline_data_importer
17 from telemetry.timeline import event_container
18 from telemetry.timeline import inspector_importer
19 from telemetry.timeline import trace_event_importer
21 # Register importers for data
24 empty_timeline_data_importer.EmptyTimelineDataImporter,
25 inspector_importer.InspectorTimelineImporter,
26 trace_event_importer.TraceEventTimelineImporter
30 class MarkerMismatchError(Exception):
32 super(MarkerMismatchError, self).__init__(
33 'Number or order of timeline markers does not match provided labels')
36 class MarkerOverlapError(Exception):
38 super(MarkerOverlapError, self).__init__(
39 'Overlapping timeline markers found')
41 def IsSliceOrAsyncSlice(t):
42 if t == async_slice_module.AsyncSlice:
44 return t == slice_module.Slice
47 class TimelineModel(event_container.TimelineEventContainer):
48 def __init__(self, timeline_data=None, shift_world_to_zero=True):
49 """ Initializes a TimelineModel. timeline_data can be a single TimelineData
50 object, a list of TimelineData objects, or None. If timeline_data is not
51 None, all events from it will be imported into the model. The events will
52 be shifted such that the first event starts at time 0, if
53 shift_world_to_zero is True.
55 super(TimelineModel, self).__init__(name='TimelineModel', parent=None)
56 self._bounds = bounds.Bounds()
57 self._thread_time_bounds = {}
59 self._browser_process = None
61 self._tab_ids_to_renderer_threads_map = {}
62 self.import_errors = []
65 if timeline_data is not None:
66 self.ImportTraces(timeline_data, shift_world_to_zero=shift_world_to_zero)
68 def IterChildContainers(self):
69 for process in self._processes.itervalues():
72 def GetAllProcesses(self):
73 return self._processes.values()
75 def GetAllThreads(self):
77 for process in self._processes.values():
78 threads.extend(process.threads.values())
87 return self._processes
90 #pylint: disable=E0202
91 def browser_process(self):
92 return self._browser_process
94 @browser_process.setter
95 #pylint: disable=E0202
96 def browser_process(self, browser_process):
97 self._browser_process = browser_process
99 def AddMappingFromTabIdToRendererThread(self, tab_id, renderer_thread):
101 raise Exception('Cannot add mapping from tab id to renderer thread once '
103 self._tab_ids_to_renderer_threads_map[tab_id] = renderer_thread
105 def ImportTraces(self, timeline_data, shift_world_to_zero=True):
107 raise Exception("Cannot add events once trace is imported")
110 if isinstance(timeline_data, list):
111 for item in timeline_data:
112 importers.append(self._CreateImporter(item))
114 importers.append(self._CreateImporter(timeline_data))
116 importers.sort(cmp=lambda x, y: x.import_priority - y.import_priority)
118 for importer in importers:
119 # TODO: catch exceptions here and add it to error list
120 importer.ImportEvents()
121 self.FinalizeImport(shift_world_to_zero, importers)
123 def FinalizeImport(self, shift_world_to_zero=False, importers=None):
124 if importers == None:
127 if not self.bounds.is_empty:
128 for process in self._processes.itervalues():
129 process.AutoCloseOpenSlices(self.bounds.max,
130 self._thread_time_bounds)
132 for importer in importers:
133 importer.FinalizeImport()
135 for process in self.processes.itervalues():
136 process.FinalizeImport()
138 if shift_world_to_zero:
139 self.ShiftWorldToZero()
142 # Because of FinalizeImport, it would probably be a good idea
143 # to prevent the timeline from from being modified.
146 def ShiftWorldToZero(self):
148 if self._bounds.is_empty:
150 shift_amount = self._bounds.min
151 for event in self.IterAllEvents():
152 event.start -= shift_amount
154 def UpdateBounds(self):
156 for event in self.IterAllEvents():
157 self._bounds.AddValue(event.start)
158 self._bounds.AddValue(event.end)
160 self._thread_time_bounds = {}
161 for thread in self.GetAllThreads():
162 self._thread_time_bounds[thread] = bounds.Bounds()
163 for event in thread.IterEventsInThisContainer(
164 event_type_predicate=lambda t: True,
165 event_predicate=lambda e: True):
166 if event.thread_start != None:
167 self._thread_time_bounds[thread].AddValue(event.thread_start)
168 if event.thread_end != None:
169 self._thread_time_bounds[thread].AddValue(event.thread_end)
171 def GetOrCreateProcess(self, pid):
172 if pid not in self._processes:
173 assert not self._frozen
174 self._processes[pid] = process_module.Process(self, pid)
175 return self._processes[pid]
177 def FindTimelineMarkers(self, timeline_marker_names):
178 """Find the timeline events with the given names.
180 If the number and order of events found does not match the names,
183 # Make sure names are in a list and remove all None names
184 if not isinstance(timeline_marker_names, list):
185 timeline_marker_names = [timeline_marker_names]
186 names = [x for x in timeline_marker_names if x is not None]
188 # Gather all events that match the names and sort them.
194 def IsEventNeeded(event):
195 if event.parent_slice != None:
197 return event.name in name_set
199 events = list(self.IterAllEvents(
201 event_type_predicate=IsSliceOrAsyncSlice,
202 event_predicate=IsEventNeeded))
203 events.sort(key=attrgetter('start'))
205 # Check if the number and order of events matches the provided names,
206 # and that the events don't overlap.
207 if len(events) != len(names):
208 raise MarkerMismatchError()
209 for (i, event) in enumerate(events):
210 if event.name != names[i]:
211 raise MarkerMismatchError()
212 for i in xrange(0, len(events)):
213 for j in xrange(i+1, len(events)):
214 if (events[j].start < events[i].start + events[i].duration):
215 raise MarkerOverlapError()
219 def GetRendererProcessFromTabId(self, tab_id):
220 renderer_thread = self.GetRendererThreadFromTabId(tab_id)
222 return renderer_thread.parent
225 def GetRendererThreadFromTabId(self, tab_id):
226 return self._tab_ids_to_renderer_threads_map.get(tab_id, None)
228 def _CreateImporter(self, event_data):
229 for importer_class in _IMPORTERS:
230 if importer_class.CanImport(event_data):
231 return importer_class(self, event_data)
232 raise ValueError("Could not find an importer for the provided event data")