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
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
13 import telemetry.core.timeline.process as tracing_process
14 from telemetry.core import web_contents
15 from telemetry.core import browser
17 # Register importers for data
18 from telemetry.core.timeline import bounds
19 from telemetry.core.timeline import empty_trace_importer
20 from telemetry.core.timeline import inspector_importer
21 from telemetry.core.timeline import trace_event_importer
24 empty_trace_importer.EmptyTraceImporter,
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')
42 class TimelineModel(object):
43 def __init__(self, event_data=None, shift_world_to_zero=True):
44 self._bounds = bounds.Bounds()
45 self._thread_time_bounds = {}
47 self._browser_process = None
49 self.import_errors = []
52 # Use a WeakKeyDictionary, because an ordinary dictionary could keep
53 # references to Tab objects around until it gets garbage collected.
54 # This would prevent telemetry from navigating to another page.
55 self._core_object_to_timeline_container_map = weakref.WeakKeyDictionary()
57 if event_data is not None:
58 self.ImportTraces([event_data], shift_world_to_zero=shift_world_to_zero)
65 def thread_time_bounds(self):
66 return self._thread_time_bounds
70 return self._processes
73 def browser_process(self):
74 return self._browser_process
76 @browser_process.setter
77 def browser_process(self, browser_process):
78 self._browser_process = browser_process
80 def ImportTraces(self, traces, shift_world_to_zero=True):
82 raise Exception("Cannot add events once recording is done")
85 for event_data in traces:
86 importers.append(self._CreateImporter(event_data))
88 importers.sort(cmp=lambda x, y: x.import_priority - y.import_priority)
90 for importer in importers:
91 # TODO: catch exceptions here and add it to error list
92 importer.ImportEvents()
93 self.FinalizeImport(shift_world_to_zero, importers)
95 def FinalizeImport(self, shift_world_to_zero=False, importers=None):
99 if not self.bounds.is_empty:
100 for process in self._processes.itervalues():
101 process.AutoCloseOpenSlices(self.bounds.max,
102 self.thread_time_bounds)
104 for importer in importers:
105 importer.FinalizeImport()
107 for process in self.processes.itervalues():
108 process.FinalizeImport()
110 if shift_world_to_zero:
111 self.ShiftWorldToZero()
114 # Because of FinalizeImport, it would probably be a good idea
115 # to prevent the timeline from from being modified.
118 def ShiftWorldToZero(self):
120 if self._bounds.is_empty:
122 shift_amount = self._bounds.min
123 for event in self.IterAllEvents():
124 event.start -= shift_amount
126 def UpdateBounds(self):
128 for event in self.IterAllEvents():
129 self._bounds.AddValue(event.start)
130 self._bounds.AddValue(event.end)
132 self._thread_time_bounds = {}
133 for thread in self.GetAllThreads():
134 self._thread_time_bounds[thread] = bounds.Bounds()
135 for event in thread.IterEventsInThisContainer():
136 if event.thread_start != None:
137 self._thread_time_bounds[thread].AddValue(event.thread_start)
138 if event.thread_end != None:
139 self._thread_time_bounds[thread].AddValue(event.thread_end)
141 def GetAllContainers(self):
144 containers.append(container)
145 for container in container.IterChildContainers():
147 for process in self._processes.itervalues():
151 def IterAllEvents(self):
152 for container in self.GetAllContainers():
153 for event in container.IterEventsInThisContainer():
156 def GetAllProcesses(self):
157 return self._processes.values()
159 def GetAllThreads(self):
161 for process in self._processes.values():
162 threads.extend(process.threads.values())
165 def GetAllEvents(self):
166 return list(self.IterAllEvents())
168 def GetAllEventsOfName(self, name, only_root_events=False):
169 events = [e for e in self.IterAllEvents() if e.name == name]
171 return filter(lambda ev: ev.parent_slice == None, events)
175 def GetEventOfName(self, name, only_root_events=False,
176 fail_if_more_than_one=False):
177 events = self.GetAllEventsOfName(name, only_root_events)
179 raise Exception('No event of name "%s" found.' % name)
180 if fail_if_more_than_one and len(events) > 1:
181 raise Exception('More than one event of name "%s" found.' % name)
184 def GetOrCreateProcess(self, pid):
185 if pid not in self._processes:
186 assert not self._frozen
187 self._processes[pid] = tracing_process.Process(self, pid)
188 return self._processes[pid]
190 def FindTimelineMarkers(self, timeline_marker_names):
191 """Find the timeline events with the given names.
193 If the number and order of events found does not match the names,
196 # Make sure names are in a list and remove all None names
197 if not isinstance(timeline_marker_names, list):
198 timeline_marker_names = [timeline_marker_names]
199 names = [x for x in timeline_marker_names if x is not None]
201 # Gather all events that match the names and sort them.
206 for name in name_set:
207 events.extend(self.GetAllEventsOfName(name, True))
208 events.sort(key=attrgetter('start'))
210 # Check if the number and order of events matches the provided names,
211 # and that the events don't overlap.
212 if len(events) != len(names):
213 raise MarkerMismatchError()
214 for (i, event) in enumerate(events):
215 if event.name != names[i]:
216 raise MarkerMismatchError()
217 for i in xrange(0, len(events)):
218 for j in xrange(i+1, len(events)):
219 if (events[j].start < events[i].start + events[i].duration):
220 raise MarkerOverlapError()
224 def GetRendererProcessFromTab(self, tab):
225 return self._core_object_to_timeline_container_map[tab]
227 def AddCoreObjectToContainerMapping(self, core_object, container):
228 """ Add a mapping from a core object to a timeline container.
230 Used for example to map a Tab to its renderer process in the timeline model.
232 assert(isinstance(core_object, web_contents.WebContents) or
233 isinstance(core_object, browser.Browser))
234 self._core_object_to_timeline_container_map[core_object] = container
236 def _CreateImporter(self, event_data):
237 for importer_class in _IMPORTERS:
238 if importer_class.CanImport(event_data):
239 return importer_class(self, event_data)
240 raise ValueError("Could not find an importer for the provided event data")