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