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 process_module 13from telemetry.timeline import async_slice as async_slice_module 14from telemetry.timeline import slice as slice_module 15from telemetry.timeline import bounds 16from telemetry.timeline import empty_timeline_data_importer 17from telemetry.timeline import event_container 18from telemetry.timeline import inspector_importer 19from telemetry.timeline import trace_event_importer 20 21# Register importers for data 22 23_IMPORTERS = [ 24 empty_timeline_data_importer.EmptyTimelineDataImporter, 25 inspector_importer.InspectorTimelineImporter, 26 trace_event_importer.TraceEventTimelineImporter 27] 28 29 30class MarkerMismatchError(Exception): 31 def __init__(self): 32 super(MarkerMismatchError, self).__init__( 33 'Number or order of timeline markers does not match provided labels') 34 35 36class MarkerOverlapError(Exception): 37 def __init__(self): 38 super(MarkerOverlapError, self).__init__( 39 'Overlapping timeline markers found') 40 41def IsSliceOrAsyncSlice(t): 42 if t == async_slice_module.AsyncSlice: 43 return True 44 return t == slice_module.Slice 45 46 47class 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. 54 """ 55 super(TimelineModel, self).__init__(name='TimelineModel', parent=None) 56 self._bounds = bounds.Bounds() 57 self._thread_time_bounds = {} 58 self._processes = {} 59 self._browser_process = None 60 self._frozen = False 61 self._tab_ids_to_renderer_threads_map = {} 62 self.import_errors = [] 63 self.metadata = [] 64 self.flow_events = [] 65 if timeline_data is not None: 66 self.ImportTraces(timeline_data, shift_world_to_zero=shift_world_to_zero) 67 68 def IterChildContainers(self): 69 for process in self._processes.itervalues(): 70 yield process 71 72 def GetAllProcesses(self): 73 return self._processes.values() 74 75 def GetAllThreads(self): 76 threads = [] 77 for process in self._processes.values(): 78 threads.extend(process.threads.values()) 79 return threads 80 81 @property 82 def bounds(self): 83 return self._bounds 84 85 @property 86 def processes(self): 87 return self._processes 88 89 @property 90 #pylint: disable=E0202 91 def browser_process(self): 92 return self._browser_process 93 94 @browser_process.setter 95 #pylint: disable=E0202 96 def browser_process(self, browser_process): 97 self._browser_process = browser_process 98 99 def AddMappingFromTabIdToRendererThread(self, tab_id, renderer_thread): 100 if self._frozen: 101 raise Exception('Cannot add mapping from tab id to renderer thread once ' 102 'trace is imported') 103 self._tab_ids_to_renderer_threads_map[tab_id] = renderer_thread 104 105 def ImportTraces(self, timeline_data, shift_world_to_zero=True): 106 if self._frozen: 107 raise Exception("Cannot add events once trace is imported") 108 109 importers = [] 110 if isinstance(timeline_data, list): 111 for item in timeline_data: 112 importers.append(self._CreateImporter(item)) 113 else: 114 importers.append(self._CreateImporter(timeline_data)) 115 116 importers.sort(cmp=lambda x, y: x.import_priority - y.import_priority) 117 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) 122 123 def FinalizeImport(self, shift_world_to_zero=False, importers=None): 124 if importers == None: 125 importers = [] 126 self.UpdateBounds() 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) 131 132 for importer in importers: 133 importer.FinalizeImport() 134 135 for process in self.processes.itervalues(): 136 process.FinalizeImport() 137 138 if shift_world_to_zero: 139 self.ShiftWorldToZero() 140 self.UpdateBounds() 141 142 # Because of FinalizeImport, it would probably be a good idea 143 # to prevent the timeline from from being modified. 144 self._frozen = True 145 146 def ShiftWorldToZero(self): 147 self.UpdateBounds() 148 if self._bounds.is_empty: 149 return 150 shift_amount = self._bounds.min 151 for event in self.IterAllEvents(): 152 event.start -= shift_amount 153 154 def UpdateBounds(self): 155 self._bounds.Reset() 156 for event in self.IterAllEvents(): 157 self._bounds.AddValue(event.start) 158 self._bounds.AddValue(event.end) 159 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) 170 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] 176 177 def FindTimelineMarkers(self, timeline_marker_names): 178 """Find the timeline events with the given names. 179 180 If the number and order of events found does not match the names, 181 raise an error. 182 """ 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] 187 188 # Gather all events that match the names and sort them. 189 events = [] 190 name_set = set() 191 for name in names: 192 name_set.add(name) 193 194 def IsEventNeeded(event): 195 if event.parent_slice != None: 196 return 197 return event.name in name_set 198 199 events = list(self.IterAllEvents( 200 recursive=True, 201 event_type_predicate=IsSliceOrAsyncSlice, 202 event_predicate=IsEventNeeded)) 203 events.sort(key=attrgetter('start')) 204 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() 216 217 return events 218 219 def GetRendererProcessFromTabId(self, tab_id): 220 renderer_thread = self.GetRendererThreadFromTabId(tab_id) 221 if renderer_thread: 222 return renderer_thread.parent 223 return None 224 225 def GetRendererThreadFromTabId(self, tab_id): 226 return self._tab_ids_to_renderer_threads_map.get(tab_id, None) 227 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") 233