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