aboutsummaryrefslogtreecommitdiff
path: root/catapult/telemetry/telemetry/timeline/model.py
diff options
context:
space:
mode:
Diffstat (limited to 'catapult/telemetry/telemetry/timeline/model.py')
-rw-r--r--catapult/telemetry/telemetry/timeline/model.py280
1 files changed, 280 insertions, 0 deletions
diff --git a/catapult/telemetry/telemetry/timeline/model.py b/catapult/telemetry/telemetry/timeline/model.py
new file mode 100644
index 00000000..c1e71a8e
--- /dev/null
+++ b/catapult/telemetry/telemetry/timeline/model.py
@@ -0,0 +1,280 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""A container for timeline-based events and traces and can handle importing
+raw event data from different sources. This model closely resembles that in the
+trace_viewer project:
+https://code.google.com/p/trace-viewer/
+"""
+
+import logging
+from operator import attrgetter
+
+from telemetry.timeline import async_slice as async_slice_module
+from telemetry.timeline import bounds
+from telemetry.timeline import event_container
+from telemetry.timeline import inspector_importer
+from telemetry.timeline import process as process_module
+from telemetry.timeline import slice as slice_module
+from telemetry.timeline import surface_flinger_importer
+from telemetry.timeline import tab_id_importer
+from telemetry.timeline import trace_data as trace_data_module
+from telemetry.timeline import trace_event_importer
+
+# Register importers for data
+
+_IMPORTERS = [
+ inspector_importer.InspectorTimelineImporter,
+ tab_id_importer.TabIdImporter,
+ trace_event_importer.TraceEventTimelineImporter,
+ surface_flinger_importer.SurfaceFlingerTimelineImporter
+]
+
+
+class MarkerMismatchError(Exception):
+ def __init__(self):
+ super(MarkerMismatchError, self).__init__(
+ 'Number or order of timeline markers does not match provided labels')
+
+
+class MarkerOverlapError(Exception):
+ def __init__(self):
+ super(MarkerOverlapError, self).__init__(
+ 'Overlapping timeline markers found')
+
+
+def IsSliceOrAsyncSlice(t):
+ if t == async_slice_module.AsyncSlice:
+ return True
+ return t == slice_module.Slice
+
+
+class TimelineModel(event_container.TimelineEventContainer):
+ def __init__(self, trace_data=None, shift_world_to_zero=True):
+ """ Initializes a TimelineModel.
+
+ Args:
+ trace_data: trace_data.TraceData containing events to import
+ shift_world_to_zero: If true, the events will be shifted such that the
+ first event starts at time 0.
+ """
+ super(TimelineModel, self).__init__(name='TimelineModel', parent=None)
+ self._bounds = bounds.Bounds()
+ self._thread_time_bounds = {}
+ self._processes = {}
+ self._browser_process = None
+ self._gpu_process = None
+ self._surface_flinger_process = None
+ self._frozen = False
+ self._tab_ids_to_renderer_threads_map = {}
+ self.import_errors = []
+ self.metadata = []
+ self.flow_events = []
+ self._global_memory_dumps = None
+ if trace_data is not None:
+ self.ImportTraces(trace_data, shift_world_to_zero=shift_world_to_zero)
+
+ def SetGlobalMemoryDumps(self, global_memory_dumps):
+ """Populates the model with a sequence of GlobalMemoryDump objects."""
+ assert not self._frozen and self._global_memory_dumps is None
+ # Keep dumps sorted in chronological order.
+ self._global_memory_dumps = tuple(sorted(global_memory_dumps,
+ key=lambda dump: dump.start))
+
+ def IterGlobalMemoryDumps(self):
+ """Iterate over the memory dump events of this model."""
+ return iter(self._global_memory_dumps or [])
+
+ def IterChildContainers(self):
+ for process in self._processes.itervalues():
+ yield process
+
+ def GetAllProcesses(self):
+ return self._processes.values()
+
+ def GetAllThreads(self):
+ threads = []
+ for process in self._processes.values():
+ threads.extend(process.threads.values())
+ return threads
+
+ @property
+ def bounds(self):
+ return self._bounds
+
+ @property
+ def processes(self):
+ return self._processes
+
+ @property
+ def browser_process(self):
+ return self._browser_process
+
+ @browser_process.setter
+ def browser_process(self, browser_process):
+ self._browser_process = browser_process
+
+ @property
+ def gpu_process(self):
+ return self._gpu_process
+
+ @gpu_process.setter
+ def gpu_process(self, gpu_process):
+ self._gpu_process = gpu_process
+
+ @property
+ def surface_flinger_process(self):
+ return self._surface_flinger_process
+
+ @surface_flinger_process.setter
+ def surface_flinger_process(self, surface_flinger_process):
+ self._surface_flinger_process = surface_flinger_process
+
+ def AddMappingFromTabIdToRendererThread(self, tab_id, renderer_thread):
+ if self._frozen:
+ raise Exception('Cannot add mapping from tab id to renderer thread once '
+ 'trace is imported')
+ self._tab_ids_to_renderer_threads_map[tab_id] = renderer_thread
+
+ def ImportTraces(self, trace_data, shift_world_to_zero=True):
+ """Populates the model with the provided trace data.
+
+ trace_data must be an instance of TraceData.
+
+ Passing shift_world_to_zero=True causes the events to be shifted such that
+ the first event starts at time 0.
+ """
+ if self._frozen:
+ raise Exception("Cannot add events once trace is imported")
+ assert isinstance(trace_data, trace_data_module.TraceData)
+
+ importers = self._CreateImporters(trace_data)
+
+ for importer in importers:
+ # TODO: catch exceptions here and add it to error list
+ importer.ImportEvents()
+ for record in trace_data.metadata_records:
+ self.metadata.append(record)
+ self.FinalizeImport(shift_world_to_zero, importers)
+
+ def FinalizeImport(self, shift_world_to_zero=False, importers=None):
+ if importers == None:
+ importers = []
+ self.UpdateBounds()
+ if not self.bounds.is_empty:
+ for process in self._processes.itervalues():
+ process.AutoCloseOpenSlices(self.bounds.max,
+ self._thread_time_bounds)
+
+ for importer in importers:
+ importer.FinalizeImport()
+
+ for process in self.processes.itervalues():
+ process.FinalizeImport()
+
+ if shift_world_to_zero:
+ self.ShiftWorldToZero()
+ self.UpdateBounds()
+
+ # Because of FinalizeImport, it would probably be a good idea
+ # to prevent the timeline from from being modified.
+ self._frozen = True
+
+ def ShiftWorldToZero(self):
+ self.UpdateBounds()
+ if self._bounds.is_empty:
+ return
+ shift_amount = self._bounds.min
+ for event in self.IterAllEvents():
+ event.start -= shift_amount
+
+ def UpdateBounds(self):
+ self._bounds.Reset()
+ for event in self.IterAllEvents():
+ self._bounds.AddValue(event.start)
+ self._bounds.AddValue(event.end)
+
+ self._thread_time_bounds = {}
+ for thread in self.GetAllThreads():
+ self._thread_time_bounds[thread] = bounds.Bounds()
+ for event in thread.IterEventsInThisContainer(
+ event_type_predicate=lambda t: True,
+ event_predicate=lambda e: True):
+ if event.thread_start != None:
+ self._thread_time_bounds[thread].AddValue(event.thread_start)
+ if event.thread_end != None:
+ self._thread_time_bounds[thread].AddValue(event.thread_end)
+
+ def GetOrCreateProcess(self, pid):
+ if pid not in self._processes:
+ assert not self._frozen
+ self._processes[pid] = process_module.Process(self, pid)
+ return self._processes[pid]
+
+ def FindTimelineMarkers(self, timeline_marker_names):
+ """Find the timeline events with the given names.
+
+ If the number and order of events found does not match the names,
+ raise an error.
+ """
+ # Make sure names are in a list and remove all None names
+ if not isinstance(timeline_marker_names, list):
+ timeline_marker_names = [timeline_marker_names]
+ names = [x for x in timeline_marker_names if x is not None]
+
+ # Gather all events that match the names and sort them.
+ events = []
+ name_set = set()
+ for name in names:
+ name_set.add(name)
+
+ def IsEventNeeded(event):
+ if event.parent_slice != None:
+ return
+ return event.name in name_set
+
+ events = list(self.IterAllEvents(
+ recursive=True,
+ event_type_predicate=IsSliceOrAsyncSlice,
+ event_predicate=IsEventNeeded))
+ events.sort(key=attrgetter('start'))
+
+ # Check if the number and order of events matches the provided names,
+ # and that the events don't overlap.
+ if len(events) != len(names):
+ raise MarkerMismatchError()
+ for (i, event) in enumerate(events):
+ if event.name != names[i]:
+ raise MarkerMismatchError()
+ for i in xrange(0, len(events)):
+ for j in xrange(i+1, len(events)):
+ if events[j].start < events[i].start + events[i].duration:
+ raise MarkerOverlapError()
+
+ return events
+
+ def GetRendererProcessFromTabId(self, tab_id):
+ renderer_thread = self.GetRendererThreadFromTabId(tab_id)
+ if renderer_thread:
+ return renderer_thread.parent
+ return None
+
+ def GetRendererThreadFromTabId(self, tab_id):
+ return self._tab_ids_to_renderer_threads_map.get(tab_id, None)
+
+ def _CreateImporters(self, trace_data):
+ def FindImporterClassForPart(part):
+ for importer_class in _IMPORTERS:
+ if importer_class.GetSupportedPart() == part:
+ return importer_class
+
+ importers = []
+ for part in trace_data.active_parts:
+ importer_class = FindImporterClassForPart(part)
+ if not importer_class:
+ logging.warning('No importer found for %s' % repr(part))
+ else:
+ importers.append(importer_class(self, trace_data))
+ importers.sort(key=lambda k: k.import_order)
+
+ return importers