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
5import json
6
7class NonSerializableTraceData(Exception):
8  """Raised when raw trace data cannot be serialized to TraceData."""
9  pass
10
11
12def _ValidateRawData(raw):
13  try:
14    json.dumps(raw)
15  except TypeError as e:
16    raise NonSerializableTraceData('TraceData is not serilizable: %s' % e)
17  except ValueError as e:
18    raise NonSerializableTraceData('TraceData is not serilizable: %s' % e)
19
20
21class TraceDataPart(object):
22  """TraceData can have a variety of events.
23
24  These are called "parts" and are accessed by the following fixed field names.
25  """
26  def __init__(self, raw_field_name):
27    self._raw_field_name = raw_field_name
28
29  def __repr__(self):
30    return 'TraceDataPart("%s")' % self._raw_field_name
31
32  @property
33  def raw_field_name(self):
34    return self._raw_field_name
35
36
37CHROME_TRACE_PART = TraceDataPart('traceEvents')
38INSPECTOR_TRACE_PART = TraceDataPart('inspectorTimelineEvents')
39SURFACE_FLINGER_PART = TraceDataPart('surfaceFlinger')
40TAB_ID_PART = TraceDataPart('tabIds')
41TELEMETRY_PART = TraceDataPart('telemetry')
42
43ALL_TRACE_PARTS = {CHROME_TRACE_PART,
44                   INSPECTOR_TRACE_PART,
45                   SURFACE_FLINGER_PART,
46                   TAB_ID_PART,
47                   TELEMETRY_PART}
48
49
50def _HasEventsFor(part, raw):
51  assert isinstance(part, TraceDataPart)
52  if part.raw_field_name not in raw:
53    return False
54  return len(raw[part.raw_field_name]) > 0
55
56
57class TraceData(object):
58  """Validates, parses, and serializes raw data.
59
60  NOTE: raw data must only include primitive objects!
61  By design, TraceData must contain only data that is BOTH json-serializable
62  to a file, AND restorable once again from that file into TraceData without
63  assistance from other classes.
64
65  Raw data can be one of three standard trace_event formats:
66  1. Trace container format: a json-parseable dict.
67  2. A json-parseable array: assumed to be chrome trace data.
68  3. A json-parseable array missing the final ']': assumed to be chrome trace
69     data.
70  """
71  def __init__(self, raw_data=None):
72    """Creates TraceData from the given data."""
73    self._raw_data = {}
74    self._events_are_safely_mutable = False
75    if not raw_data:
76      return
77    _ValidateRawData(raw_data)
78
79    if isinstance(raw_data, basestring):
80      if raw_data.startswith('[') and not raw_data.endswith(']'):
81        if raw_data.endswith(','):
82          raw_data = raw_data[:-1]
83        raw_data += ']'
84      json_data = json.loads(raw_data)
85      # The parsed data isn't shared with anyone else, so we mark this value
86      # as safely mutable.
87      self._events_are_safely_mutable = True
88    else:
89      json_data = raw_data
90
91    if isinstance(json_data, dict):
92      self._raw_data = json_data
93    elif isinstance(json_data, list):
94      if len(json_data) == 0:
95        self._raw_data = {}
96      self._raw_data = {CHROME_TRACE_PART.raw_field_name: json_data}
97    else:
98      raise Exception('Unrecognized data format.')
99
100  def _SetFromBuilder(self, d):
101    self._raw_data = d
102    self._events_are_safely_mutable = True
103
104  @property
105  def events_are_safely_mutable(self):
106    """Returns true if the events in this value are completely sealed.
107
108    Some importers want to take complex fields out of the TraceData and add
109    them to the model, changing them subtly as they do so. If the TraceData
110    was constructed with data that is shared with something outside the trace
111    data, for instance a test harness, then this mutation is unexpected. But,
112    if the values are sealed, then mutating the events is a lot faster.
113
114    We know if events are sealed if the value came from a string, or if the
115    value came from a TraceDataBuilder.
116    """
117    return self._events_are_safely_mutable
118
119  @property
120  def active_parts(self):
121    return {p for p in ALL_TRACE_PARTS if p.raw_field_name in self._raw_data}
122
123  @property
124  def metadata_records(self):
125    part_field_names = {p.raw_field_name for p in ALL_TRACE_PARTS}
126    for k, v in self._raw_data.iteritems():
127      if k in part_field_names:
128        continue
129      yield {
130        'name': k,
131        'value': v
132      }
133
134  def HasEventsFor(self, part):
135    return _HasEventsFor(part, self._raw_data)
136
137  def GetEventsFor(self, part):
138    if not self.HasEventsFor(part):
139      return []
140    assert isinstance(part, TraceDataPart)
141    return self._raw_data[part.raw_field_name]
142
143  def Serialize(self, f, gzip_result=False):
144    """Serializes the trace result to a file-like object.
145
146    Always writes in the trace container format.
147    """
148    assert not gzip_result, 'Not implemented'
149    json.dump(self._raw_data, f)
150
151
152class TraceDataBuilder(object):
153  """TraceDataBuilder helps build up a trace from multiple trace agents.
154
155  TraceData is supposed to be immutable, but it is useful during recording to
156  have a mutable version. That is TraceDataBuilder.
157  """
158  def __init__(self):
159    self._raw_data = {}
160
161  def AsData(self):
162    if self._raw_data == None:
163      raise Exception('Can only AsData once')
164
165    data = TraceData()
166    data._SetFromBuilder(self._raw_data)
167    self._raw_data = None
168    return data
169
170  def AddEventsTo(self, part, events):
171    """Note: this won't work when called from multiple browsers.
172
173    Each browser's trace_event_impl zeros its timestamps when it writes them
174    out and doesn't write a timebase that can be used to re-sync them.
175    """
176    assert isinstance(part, TraceDataPart)
177    assert isinstance(events, list)
178    if self._raw_data == None:
179      raise Exception('Already called AsData() on this builder.')
180
181    self._raw_data.setdefault(part.raw_field_name, []).extend(events)
182
183  def HasEventsFor(self, part):
184    return _HasEventsFor(part, self._raw_data)
185