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