Index: tools/telemetry/telemetry/value/trace.py |
diff --git a/tools/telemetry/telemetry/value/trace.py b/tools/telemetry/telemetry/value/trace.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..3d17aeefe54a7786ca9a18e230f6964d4e92be28 |
--- /dev/null |
+++ b/tools/telemetry/telemetry/value/trace.py |
@@ -0,0 +1,194 @@ |
+# 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 |
+ |
+def _ValidateRawData(raw): |
+ if isinstance(raw, basestring): |
+ return |
+ if isinstance(raw, numbers.Number): |
+ return |
+ |
nednguyen
2014/08/05 14:34:34
Can None be raw data?
For example: {1:None, 2:'foo
|
+ if isinstance(raw, list): |
+ for r in raw: |
+ _ValidateRawData(r) |
+ return |
+ |
+ if isinstance(raw, dict): |
+ for k,v in raw.iteritems(): |
+ _ValidateRawData(k) |
+ _ValidateRawData(v) |
+ return |
+ |
+ raise Exception('%s is not allowed in TraceValue', raw) |
nednguyen
2014/08/05 14:34:34
Throwing exception at the leaf level may make it h
|
+ |
+ |
+# TraceValues have a variety of events in them. These are called "parts" and |
+# aare accessed by name, using the following fixed field names. |
nednguyen
2014/08/05 14:34:34
nit: are
|
+class TraceValuePart(object): |
chrishenry
2014/08/05 23:44:15
Just TracePart? TraceValuePart is a bit ambiguous.
|
+ def __init__(self, rawFieldName): |
+ self.rawFieldName = rawFieldName |
chrishenry
2014/08/05 23:44:15
nit: raw_field_name
|
+ |
+ def __repr__(self): |
+ return 'TraceValuePart("%s")' % self.rawFieldName |
+ |
+CHROME_TRACE_PART = TraceValuePart('traceEvents') |
+INSPECTOR_TRACE_PART = TraceValuePart('inspectorTimelineEvents') |
+TAB_ID_PART = TraceValuePart('tabIds') |
+ |
+ALL_TRACE_VALUE_PARTS = set([CHROME_TRACE_PART, |
+ INSPECTOR_TRACE_PART, |
+ TAB_ID_PART]) |
+ |
+class RawTraceValueHelpersMixin(object): |
nednguyen
2014/08/05 14:34:34
Don't you need __init__(self) to define self._raw_
chrishenry
2014/08/05 23:44:16
Does TraceValue and Builder really need to mixin t
|
+ @property |
+ def active_parts(self): |
+ return set([p for p in ALL_TRACE_VALUE_PARTS |
+ if p.rawFieldName in self._raw_data]) |
+ |
+ def HasEventsFor(self, part): |
+ assert isinstance(part, TraceValuePart) |
+ if part.rawFieldName not in self._raw_data: |
+ return False |
+ return len(self._raw_data[part.rawFieldName]) > 0 |
+ |
+ def GetEventsFor(self, part): |
+ assert isinstance(part, TraceValuePart) |
+ return self._raw_data[part.rawFieldName] |
+ |
+ @property |
+ def metadata_records(self): |
+ part_field_names = set([p.rawFieldName for p in ALL_TRACE_VALUE_PARTS]) |
slamm
2014/08/12 23:11:59
You can build the set with a generator expression
|
+ for k,v in self._raw_data.iteritems(): |
chrishenry
2014/08/05 23:44:15
nit: k, v (space after ,)
|
+ if k in part_field_names: |
+ continue |
+ yield { |
+ 'name' : k, |
chrishenry
2014/08/05 23:44:15
nit: no space before :
|
+ 'value' : self._raw_data[v] |
nednguyen
2014/08/05 14:34:34
'value': v ?
|
+ } |
+ |
+ |
+# TODO(nduca): Make this subclass value. Moving it was a huge enough patch |
+# that the true subclassing will be done in a followup. |
+class TraceValue(RawTraceValueHelpersMixin): |
+ def __init__(self, raw_data=None): |
+ """Creates a TraceValue from the given data. |
+ |
+ NOTE: raw data must NOT include any non-primitive objects! |
+ By design, TraceValue must contain only data that is BOTH serializable |
+ to a file, AND restorable once again from that file into a TraceValue |
+ without assistance from other classes. |
+ |
+ raw_data can be any of the three standard trace_event formats: |
nednguyen
2014/08/05 14:34:34
So we will need something else for video and image
|
+ 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. |
+ """ |
+ self._raw_data = {} |
+ self._events_are_safely_mutable = False |
+ if raw_data == None: |
+ return |
+ |
+ _ValidateRawData(raw_data) |
+ |
+ if self._TryInitFromRaw(raw_data): |
+ return |
+ |
+ if not isinstance(raw_data, basestring): |
chrishenry
2014/08/05 23:44:16
I think it's clearer to refactor TryInitFromRaw an
|
+ raise ValueError('Unrecognized data format') |
+ |
+ if len(raw_data) == 0: |
+ self._raw_data == {} |
slamm
2014/08/12 23:11:59
self._raw_data = {} ?
Same in _TryInitFromRaw.
|
+ return |
+ |
+ if raw_data[0] == '[' and raw_data[-1] != ']': |
slamm
2014/08/12 23:11:59
Better?
if raw_data.startswith('[') and not raw_d
|
+ if raw_data[-1] == ',': |
+ raw_data = raw_data[:-1] + ']' |
+ else: |
+ raw_data = raw_data + ']' |
+ |
+ raw_data = json.loads(raw_data) |
+ if self._TryInitFromRaw(raw_data): |
+ self._events_are_safely_mutable = True |
+ return |
+ |
+ raise Exception('Unrecognized data format') |
+ |
+ def _TryInitFromRaw(self, raw_data): |
+ if isinstance(raw_data, dict): |
+ self._raw_data = raw_data |
+ return True |
+ |
+ if isinstance(raw_data, list): |
+ if len(raw_data) == 0: |
+ self._raw_data == {} |
+ return True |
+ |
+ self._raw_data = {'traceEvents': raw_data} |
nednguyen
2014/08/05 14:34:34
How about self._raw_data = { CHROME_TRACE_PART.raw
|
+ return True |
+ |
+ return False |
+ |
+ def _SetFromBuilder(self, d): |
+ self._raw_data = d |
+ self._event_data_is_mutable = True |
chrishenry
2014/08/05 23:44:16
Unused? Or do you mean _events_are_safely_mutable?
|
+ |
+ @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 TraceValue and add |
+ them to the model, changing them subtly as they do so. If the TraceValue was |
chrishenry
2014/08/05 23:44:16
Do these importers modify the raw data in this Tra
|
+ constructed with data that is shared with something outside the trace value, |
+ 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 |
chrishenry
2014/08/05 23:44:16
incomplete sentence?
|
+ """ |
+ return self._events_are_safely_mutable |
+ |
+ 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 TraceValueBuilder(RawTraceValueHelpersMixin): |
+ def __init__(self): |
+ """TraceValueBuilder helps build up of a trace from multiple trace agents. |
+ |
+ TraceValue is supposed to be immutable, but it is useful during recording |
+ to have a mutable version. That is TraceValueBuilder. |
+ """ |
+ self._raw_data = {} |
+ |
+ def AsValue(self): |
+ if self._raw_data == None: |
+ raise Exception('Can only AsValue() once') |
+ |
+ res = TraceValue() |
chrishenry
2014/08/05 23:44:16
nit: s/res/value/
|
+ res._SetFromBuilder(self._raw_data) |
+ self._raw_data = None |
+ return res |
+ |
+ def AddEventsTo(self, part, events): |
+ # Note: this wont work when you call this 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. |
chrishenry
2014/08/05 23:44:16
Turn this into pydoc?
|
+ assert isinstance(part, TraceValuePart) |
+ assert isinstance(events, list) |
+ if self._raw_data == None: |
+ raise Exception('Already called AsValue() on this builder.') |
+ existing_events = None |
+ existing_events = self._raw_data.get(part.rawFieldName, None) |
+ if existing_events == None: |
+ existing_events = [] |
+ self._raw_data[part.rawFieldName] = existing_events |
+ existing_events.extend(events) |
+ |