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 10import logging 11from operator import attrgetter 12 13from telemetry.timeline import async_slice as async_slice_module 14from telemetry.timeline import bounds 15from telemetry.timeline import event_container 16from telemetry.timeline import inspector_importer 17from telemetry.timeline import process as process_module 18from telemetry.timeline import slice as slice_module 19from telemetry.timeline import surface_flinger_importer 20from telemetry.timeline import tab_id_importer 21from telemetry.timeline import trace_data as trace_data_module 22from telemetry.timeline import trace_event_importer 23 24# Register importers for data 25 26_IMPORTERS = [ 27 inspector_importer.InspectorTimelineImporter, 28 tab_id_importer.TabIdImporter, 29 trace_event_importer.TraceEventTimelineImporter, 30 surface_flinger_importer.SurfaceFlingerTimelineImporter 31] 32 33 34class MarkerMismatchError(Exception): 35 def __init__(self): 36 super(MarkerMismatchError, self).__init__( 37 'Number or order of timeline markers does not match provided labels') 38 39 40class MarkerOverlapError(Exception): 41 def __init__(self): 42 super(MarkerOverlapError, self).__init__( 43 'Overlapping timeline markers found') 44 45 46def IsSliceOrAsyncSlice(t): 47 if t == async_slice_module.AsyncSlice: 48 return True 49 return t == slice_module.Slice 50 51 52class TimelineModel(event_container.TimelineEventContainer): 53 def __init__(self, trace_data=None, shift_world_to_zero=True): 54 """ Initializes a TimelineModel. 55 56 Args: 57 trace_data: trace_data.TraceData containing events to import 58 shift_world_to_zero: If true, the events will be shifted such that the 59 first event starts at time 0. 60 """ 61 super(TimelineModel, self).__init__(name='TimelineModel', parent=None) 62 self._bounds = bounds.Bounds() 63 self._thread_time_bounds = {} 64 self._processes = {} 65 self._browser_process = None 66 self._gpu_process = None 67 self._surface_flinger_process = None 68 self._frozen = False 69 self._tab_ids_to_renderer_threads_map = {} 70 self.import_errors = [] 71 self.metadata = [] 72 self.flow_events = [] 73 self._global_memory_dumps = None 74 if trace_data is not None: 75 self.ImportTraces(trace_data, shift_world_to_zero=shift_world_to_zero) 76 77 def SetGlobalMemoryDumps(self, global_memory_dumps): 78 """Populates the model with a sequence of GlobalMemoryDump objects.""" 79 assert not self._frozen and self._global_memory_dumps is None 80 # Keep dumps sorted in chronological order. 81 self._global_memory_dumps = tuple(sorted(global_memory_dumps, 82 key=lambda dump: dump.start)) 83 84 def IterGlobalMemoryDumps(self): 85 """Iterate over the memory dump events of this model.""" 86 return iter(self._global_memory_dumps or []) 87 88 def IterChildContainers(self): 89 for process in self._processes.itervalues(): 90 yield process 91 92 def GetAllProcesses(self): 93 return self._processes.values() 94 95 def GetAllThreads(self): 96 threads = [] 97 for process in self._processes.values(): 98 threads.extend(process.threads.values()) 99 return threads 100 101 @property 102 def bounds(self): 103 return self._bounds 104 105 @property 106 def processes(self): 107 return self._processes 108 109 @property 110 def browser_process(self): 111 return self._browser_process 112 113 @browser_process.setter 114 def browser_process(self, browser_process): 115 self._browser_process = browser_process 116 117 @property 118 def gpu_process(self): 119 return self._gpu_process 120 121 @gpu_process.setter 122 def gpu_process(self, gpu_process): 123 self._gpu_process = gpu_process 124 125 @property 126 def surface_flinger_process(self): 127 return self._surface_flinger_process 128 129 @surface_flinger_process.setter 130 def surface_flinger_process(self, surface_flinger_process): 131 self._surface_flinger_process = surface_flinger_process 132 133 def AddMappingFromTabIdToRendererThread(self, tab_id, renderer_thread): 134 if self._frozen: 135 raise Exception('Cannot add mapping from tab id to renderer thread once ' 136 'trace is imported') 137 self._tab_ids_to_renderer_threads_map[tab_id] = renderer_thread 138 139 def ImportTraces(self, trace_data, shift_world_to_zero=True): 140 """Populates the model with the provided trace data. 141 142 trace_data must be an instance of TraceData. 143 144 Passing shift_world_to_zero=True causes the events to be shifted such that 145 the first event starts at time 0. 146 """ 147 if self._frozen: 148 raise Exception("Cannot add events once trace is imported") 149 assert isinstance(trace_data, trace_data_module.TraceData) 150 151 importers = self._CreateImporters(trace_data) 152 153 for importer in importers: 154 # TODO: catch exceptions here and add it to error list 155 importer.ImportEvents() 156 for record in trace_data.metadata_records: 157 self.metadata.append(record) 158 self.FinalizeImport(shift_world_to_zero, importers) 159 160 def FinalizeImport(self, shift_world_to_zero=False, importers=None): 161 if importers == None: 162 importers = [] 163 self.UpdateBounds() 164 if not self.bounds.is_empty: 165 for process in self._processes.itervalues(): 166 process.AutoCloseOpenSlices(self.bounds.max, 167 self._thread_time_bounds) 168 169 for importer in importers: 170 importer.FinalizeImport() 171 172 for process in self.processes.itervalues(): 173 process.FinalizeImport() 174 175 if shift_world_to_zero: 176 self.ShiftWorldToZero() 177 self.UpdateBounds() 178 179 # Because of FinalizeImport, it would probably be a good idea 180 # to prevent the timeline from from being modified. 181 self._frozen = True 182 183 def ShiftWorldToZero(self): 184 self.UpdateBounds() 185 if self._bounds.is_empty: 186 return 187 shift_amount = self._bounds.min 188 for event in self.IterAllEvents(): 189 event.start -= shift_amount 190 191 def UpdateBounds(self): 192 self._bounds.Reset() 193 for event in self.IterAllEvents(): 194 self._bounds.AddValue(event.start) 195 self._bounds.AddValue(event.end) 196 197 self._thread_time_bounds = {} 198 for thread in self.GetAllThreads(): 199 self._thread_time_bounds[thread] = bounds.Bounds() 200 for event in thread.IterEventsInThisContainer( 201 event_type_predicate=lambda t: True, 202 event_predicate=lambda e: True): 203 if event.thread_start != None: 204 self._thread_time_bounds[thread].AddValue(event.thread_start) 205 if event.thread_end != None: 206 self._thread_time_bounds[thread].AddValue(event.thread_end) 207 208 def GetOrCreateProcess(self, pid): 209 if pid not in self._processes: 210 assert not self._frozen 211 self._processes[pid] = process_module.Process(self, pid) 212 return self._processes[pid] 213 214 def FindTimelineMarkers(self, timeline_marker_names): 215 """Find the timeline events with the given names. 216 217 If the number and order of events found does not match the names, 218 raise an error. 219 """ 220 # Make sure names are in a list and remove all None names 221 if not isinstance(timeline_marker_names, list): 222 timeline_marker_names = [timeline_marker_names] 223 names = [x for x in timeline_marker_names if x is not None] 224 225 # Gather all events that match the names and sort them. 226 events = [] 227 name_set = set() 228 for name in names: 229 name_set.add(name) 230 231 def IsEventNeeded(event): 232 if event.parent_slice != None: 233 return 234 return event.name in name_set 235 236 events = list(self.IterAllEvents( 237 recursive=True, 238 event_type_predicate=IsSliceOrAsyncSlice, 239 event_predicate=IsEventNeeded)) 240 events.sort(key=attrgetter('start')) 241 242 # Check if the number and order of events matches the provided names, 243 # and that the events don't overlap. 244 if len(events) != len(names): 245 raise MarkerMismatchError() 246 for (i, event) in enumerate(events): 247 if event.name != names[i]: 248 raise MarkerMismatchError() 249 for i in xrange(0, len(events)): 250 for j in xrange(i+1, len(events)): 251 if events[j].start < events[i].start + events[i].duration: 252 raise MarkerOverlapError() 253 254 return events 255 256 def GetRendererProcessFromTabId(self, tab_id): 257 renderer_thread = self.GetRendererThreadFromTabId(tab_id) 258 if renderer_thread: 259 return renderer_thread.parent 260 return None 261 262 def GetRendererThreadFromTabId(self, tab_id): 263 return self._tab_ids_to_renderer_threads_map.get(tab_id, None) 264 265 def _CreateImporters(self, trace_data): 266 def FindImporterClassForPart(part): 267 for importer_class in _IMPORTERS: 268 if importer_class.GetSupportedPart() == part: 269 return importer_class 270 271 importers = [] 272 for part in trace_data.active_parts: 273 importer_class = FindImporterClassForPart(part) 274 if not importer_class: 275 logging.warning('No importer found for %s' % repr(part)) 276 else: 277 importers.append(importer_class(self, trace_data)) 278 importers.sort(key=lambda k: k.import_order) 279 280 return importers 281