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