Index: tools/telemetry/telemetry/timeline/trace_data.py |
diff --git a/tools/telemetry/telemetry/timeline/trace_data.py b/tools/telemetry/telemetry/timeline/trace_data.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..859ec83fe293ef776bd9191d3de27526a8284ca2 |
--- /dev/null |
+++ b/tools/telemetry/telemetry/timeline/trace_data.py |
@@ -0,0 +1,183 @@ |
+# 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. |
+ |
+import json |
+import numbers |
+ |
+class NonSerializableTraceData(Exception): |
+ """Raised when raw trace data cannot be serialized to TraceData.""" |
+ pass |
+ |
+ |
+def _ValidateRawData(raw): |
+ try: |
+ json.dumps(raw) |
+ except TypeError as e: |
+ raise NonSerializableTraceData('TraceData is not serilizable: %s' % e) |
+ except ValueError as e: |
+ raise NonSerializableTraceData('TraceData is not serilizable: %s' % e) |
+ |
+ |
+class TraceDataPart(object): |
+ """TraceData can have a variety of events. |
+ |
+ These are called "parts" and are accessed by the following fixed field names. |
+ """ |
+ def __init__(self, raw_field_name): |
+ self._raw_field_name = raw_field_name |
+ |
+ def __repr__(self): |
+ return 'TraceDataPart("%s")' % self._raw_field_name |
+ |
+ @property |
+ def raw_field_name(self): |
+ return self._raw_field_name |
+ |
+ |
+CHROME_TRACE_PART = TraceDataPart('traceEvents') |
+INSPECTOR_TRACE_PART = TraceDataPart('inspectorTimelineEvents') |
+SURFACE_FLINGER_PART = TraceDataPart('surfaceFlinger') |
+TAB_ID_PART = TraceDataPart('tabIds') |
+ |
+ALL_TRACE_PARTS = {CHROME_TRACE_PART, |
+ INSPECTOR_TRACE_PART, |
+ SURFACE_FLINGER_PART, |
+ TAB_ID_PART} |
+ |
+ |
+def _HasEventsFor(part, raw): |
+ assert isinstance(part, TraceDataPart) |
+ if part.raw_field_name not in raw: |
+ return False |
+ return len(raw[part.raw_field_name]) > 0 |
+ |
+ |
+class TraceData(object): |
+ """Validates, parses, and serializes raw data. |
+ |
+ NOTE: raw data must only include primitive objects! |
+ By design, TraceData must contain only data that is BOTH json-serializable |
+ to a file, AND restorable once again from that file into TraceData without |
+ assistance from other classes. |
+ |
+ Raw data can be one of three standard trace_event formats: |
+ 1. Trace container format: a json-parseable dict. |
+ 2. A json-parseable array: assumed to be chrome trace data. |
+ 3. A json-parseable array missing the final ']': assumed to be chrome trace |
+ data. |
+ """ |
+ def __init__(self, raw_data=None): |
+ """Creates TraceData from the given data.""" |
+ self._raw_data = {} |
+ self._events_are_safely_mutable = False |
+ if not raw_data: |
+ return |
+ _ValidateRawData(raw_data) |
+ |
+ if isinstance(raw_data, basestring): |
+ if raw_data.startswith('[') and not raw_data.endswith(']'): |
+ if raw_data.endswith(','): |
+ raw_data = raw_data[:-1] |
+ raw_data += ']' |
+ json_data = json.loads(raw_data) |
+ # The parsed data isn't shared with anyone else, so we mark this value |
+ # as safely mutable. |
+ self._events_are_safely_mutable = True |
+ else: |
+ json_data = raw_data |
+ |
+ if isinstance(json_data, dict): |
+ self._raw_data = json_data |
+ elif isinstance(json_data, list): |
+ if len(json_data) == 0: |
+ self._raw_data = {} |
+ self._raw_data = {CHROME_TRACE_PART.raw_field_name: json_data} |
+ else: |
+ raise Exception('Unrecognized data format.') |
+ |
+ def _SetFromBuilder(self, d): |
+ self._raw_data = d |
+ self._events_are_safely_mutable = True |
+ |
+ @property |
+ def events_are_safely_mutable(self): |
+ """Returns true if the events in this value are completely sealed. |
+ |
+ Some importers want to take complex fields out of the TraceData and add |
+ them to the model, changing them subtly as they do so. If the TraceData |
+ was constructed with data that is shared with something outside the trace |
+ data, for instance a test harness, then this mutation is unexpected. But, |
+ if the values are sealed, then mutating the events is a lot faster. |
+ |
+ We know if events are sealed if the value came from a string, or if the |
+ value came from a TraceDataBuilder. |
+ """ |
+ return self._events_are_safely_mutable |
+ |
+ @property |
+ def active_parts(self): |
+ return {p for p in ALL_TRACE_PARTS if p.raw_field_name in self._raw_data} |
+ |
+ @property |
+ def metadata_records(self): |
+ part_field_names = {p.raw_field_name for p in ALL_TRACE_PARTS} |
+ for k, v in self._raw_data.iteritems(): |
+ if k in part_field_names: |
+ continue |
+ yield { |
+ 'name': k, |
+ 'value': self._raw_data[v] |
+ } |
+ |
+ def HasEventsFor(self, part): |
+ return _HasEventsFor(part, self._raw_data) |
+ |
+ def GetEventsFor(self, part): |
+ if not self.HasEventsFor(part): |
+ return [] |
+ assert isinstance(part, TraceDataPart) |
+ return self._raw_data[part.raw_field_name] |
+ |
+ def Serialize(self, f, gzip_result=False): |
+ """Serializes the trace result to a file-like object. |
+ |
+ Always writes in the trace container format. |
+ """ |
+ assert not gzip_result, 'Not implemented' |
+ json.dump(self._raw_data, f) |
+ |
+ |
+class TraceDataBuilder(object): |
+ """TraceDataBuilder helps build up a trace from multiple trace agents. |
+ |
+ TraceData is supposed to be immutable, but it is useful during recording to |
+ have a mutable version. That is TraceDataBuilder. |
+ """ |
+ def __init__(self): |
+ self._raw_data = {} |
+ |
+ def AsData(self): |
+ if self._raw_data == None: |
+ raise Exception('Can only AsData once') |
+ |
+ data = TraceData() |
+ data._SetFromBuilder(self._raw_data) |
+ self._raw_data = None |
+ return data |
+ |
+ def AddEventsTo(self, part, events): |
+ """Note: this won't work when called from multiple browsers. |
+ |
+ Each browser's trace_event_impl zeros its timestamps when it writes them |
+ out and doesn't write a timebase that can be used to re-sync them. |
+ """ |
+ assert isinstance(part, TraceDataPart) |
+ assert isinstance(events, list) |
+ if self._raw_data == None: |
+ raise Exception('Already called AsData() on this builder.') |
+ |
+ self._raw_data.setdefault(part.raw_field_name, []).extend(events) |
+ |
+ def HasEventsFor(self, part): |
+ return _HasEventsFor(part, self._raw_data) |