Chromium Code Reviews| 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..9b52fce2fe9248bb8882c775710fbfc3f22f3a93 |
| --- /dev/null |
| +++ b/tools/telemetry/telemetry/timeline/trace_data.py |
| @@ -0,0 +1,175 @@ |
| +# 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 |
| + |
|
slamm
2014/12/22 17:12:32
It is good practice for modules to raise their own
|
| +def _ValidateRawData(raw): |
| + try: |
| + json.dumps(raw) |
| + except TypeError as e: |
| + raise Exception('TraceData cannot be serialized: %s' % e) |
| + except ValueError as e: |
| + raise Exception('TraceData cannot be serialized: %s' % e) |
|
slamm
2014/12/22 17:12:31
except (TypeError, ValueError) as e:
raise NonSe
sullivan
2014/12/22 19:28:37
TraceData should only contain json-serializable da
|
| + |
| + |
| +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 |
|
slamm
2014/12/22 17:12:32
indent 2.
sullivan
2014/12/22 19:28:38
Done.
|
| + |
| + |
| +CHROME_TRACE_PART = TraceDataPart('traceEvents') |
| +INSPECTOR_TRACE_PART = TraceDataPart('inspectorTimelineEvents') |
| +SURFACE_FLINGER_PART = TraceDataPart('surfaceFlinger') |
| +TAB_ID_PART = TraceDataPart('tabIds') |
| + |
| +ALL_TRACE_PARTS = set([CHROME_TRACE_PART, INSPECTOR_TRACE_PART, TAB_ID_PART]) |
|
slamm
2014/12/22 17:12:32
Use set literal?
ALL_TRACE_PARTS = {CHROME_TRACE_
sullivan
2014/12/22 19:28:38
Done.
|
| + |
| + |
| +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 NOT include any non-primitive objects! |
|
slamm
2014/12/22 17:12:32
NOTE: raw data must only include primitive objects
sullivan
2014/12/22 19:28:38
Done.
|
| + 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 any of the three standard trace_event formats: |
|
slamm
2014/12/22 17:12:32
Raw data can be one of three standard trace_event
sullivan
2014/12/22 19:28:38
Done.
|
| + 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 set([p for p in ALL_TRACE_PARTS |
| + if p.raw_field_name in self._raw_data]) |
|
slamm
2014/12/22 17:12:31
Use a set comprehension?
return {p for p in ALL
sullivan
2014/12/22 19:28:38
Done.
|
| + |
| + @property |
| + def metadata_records(self): |
| + part_field_names = set([p.raw_field_name for p in ALL_TRACE_PARTS]) |
|
slamm
2014/12/22 17:12:32
part_field_names = {p.raw_field_name for p in ALL_
sullivan
2014/12/22 19:28:37
Done.
|
| + for k,v in self._raw_data.iteritems(): |
|
slamm
2014/12/22 17:12:32
A space after the comma.
for k, v in self._raw_da
sullivan
2014/12/22 19:28:37
Done.
|
| + if k in part_field_names: |
| + continue |
| + yield { |
| + 'name' : k, |
| + 'value' : self._raw_data[v] |
|
slamm
2014/12/22 17:12:32
No spaces before the colons.
'name': k,
'value':
sullivan
2014/12/22 19:28:38
Done.
|
| + } |
| + |
| + 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.""" |
|
slamm
2014/12/22 17:12:32
Closing quotes on a new line.
"""Serializes the t
sullivan
2014/12/22 19:28:38
Done.
|
| + 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) |