Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(5)

Unified Diff: tools/android/loading/tracing.py

Issue 2763733002: tools/android/loading: rename tracing to tracing_track (Closed)
Patch Set: also rename the unittest Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « tools/android/loading/test_utils.py ('k') | tools/android/loading/tracing_track.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/android/loading/tracing.py
diff --git a/tools/android/loading/tracing.py b/tools/android/loading/tracing.py
deleted file mode 100644
index f69392da78f98b363b2246c3729bd25ea3eb9bd4..0000000000000000000000000000000000000000
--- a/tools/android/loading/tracing.py
+++ /dev/null
@@ -1,571 +0,0 @@
-# Copyright 2016 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.
-
-"""Monitor tracing events on chrome via chrome remote debugging."""
-
-import itertools
-import logging
-import operator
-
-import clovis_constants
-import devtools_monitor
-
-
-class TracingTrack(devtools_monitor.Track):
- """Grabs and processes trace event messages.
-
- See https://goo.gl/Qabkqk for details on the protocol.
- """
- def __init__(self, connection, categories, fetch_stream=False):
- """Initialize this TracingTrack.
-
- Args:
- connection: a DevToolsConnection.
- categories: ([str] or None) If set, a list of categories to enable or
- disable in Chrome tracing. Categories prefixed with '-' are
- disabled.
- fetch_stream: if true, use a websocket stream to fetch tracing data rather
- than dataCollected events. It appears based on very limited testing that
- a stream is slower than the default reporting as dataCollected events.
- """
- super(TracingTrack, self).__init__(connection)
- if connection:
- connection.RegisterListener('Tracing.dataCollected', self)
-
- self._categories = set(categories)
- params = {}
- params['categories'] = ','.join(self._categories)
- if fetch_stream:
- params['transferMode'] = 'ReturnAsStream'
-
- if connection:
- connection.SyncRequestNoResponse('Tracing.start', params)
-
- self._events = []
- self._base_msec = None
- self._interval_tree = None
- self._main_frame_id = None
-
- def Handle(self, method, event):
- for e in event['params']['value']:
- event = Event(e)
- self._events.append(event)
- if self._base_msec is None or event.start_msec < self._base_msec:
- self._base_msec = event.start_msec
- # Invalidate our index rather than trying to be fancy and incrementally
- # update.
- self._interval_tree = None
-
- def Categories(self):
- """Returns the set of categories in this trace."""
- return self._categories
-
- def GetFirstEventMillis(self):
- """Find the canonical start time for this track.
-
- Returns:
- The millisecond timestamp of the first request.
- """
- return self._base_msec
-
- def GetEvents(self):
- """Returns a list of tracing.Event. Not sorted."""
- return self._events
-
- def GetMatchingEvents(self, category, name):
- """Gets events matching |category| and |name|."""
- return [e for e in self.GetEvents() if e.Matches(category, name)]
-
- def GetMatchingMainFrameEvents(self, category, name):
- """Gets events matching |category| and |name| that occur in the main frame.
-
- Events without a 'frame' key in their |args| are discarded.
- """
- matching_events = self.GetMatchingEvents(category, name)
- return [e for e in matching_events
- if 'frame' in e.args and e.args['frame'] == self.GetMainFrameID()]
-
- def GetMainFrameRoutingID(self):
- """Returns the main frame routing ID."""
- for event in self.GetMatchingEvents(
- 'navigation', 'RenderFrameImpl::OnNavigate'):
- return event.args['id']
- assert False
-
- def GetMainFrameID(self):
- """Returns the main frame ID."""
- if not self._main_frame_id:
- navigation_start_events = self.GetMatchingEvents(
- 'blink.user_timing', 'navigationStart')
- first_event = min(navigation_start_events, key=lambda e: e.start_msec)
- self._main_frame_id = first_event.args['frame']
-
- return self._main_frame_id
-
- def SetMainFrameID(self, frame_id):
- """Set the main frame ID. Normally this is used only for testing."""
- self._main_frame_id = frame_id
-
- def EventsAt(self, msec):
- """Gets events active at a timestamp.
-
- Args:
- msec: tracing milliseconds to query. Tracing milliseconds appears to be
- since chrome startup (ie, arbitrary epoch).
-
- Returns:
- List of events active at that timestamp. Instantaneous (ie, instant,
- sample and counter) events are never included. Event end times are
- exclusive, so that an event ending at the usec parameter will not be
- returned.
- """
- self._IndexEvents()
- return self._interval_tree.EventsAt(msec)
-
- def Filter(self, pid=None, tid=None, categories=None):
- """Returns a new TracingTrack with a subset of the events.
-
- Args:
- pid: (int or None) Selects events from this PID.
- tid: (int or None) Selects events from this TID.
- categories: (set([str]) or None) Selects events belonging to one of the
- categories.
- """
- events = self._events
- if pid is not None:
- events = filter(lambda e : e.tracing_event['pid'] == pid, events)
- if tid is not None:
- events = filter(lambda e : e.tracing_event['tid'] == tid, events)
- if categories is not None:
- events = filter(
- lambda e : set(e.category.split(',')).intersection(categories),
- events)
- tracing_track = TracingTrack(None, clovis_constants.DEFAULT_CATEGORIES)
- tracing_track._events = events
- tracing_track._categories = self._categories
- if categories is not None:
- tracing_track._categories = self._categories.intersection(categories)
- return tracing_track
-
- def ToJsonDict(self):
- return {'categories': list(self._categories),
- 'events': [e.ToJsonDict() for e in self._events]}
-
- @classmethod
- def FromJsonDict(cls, json_dict):
- if not json_dict:
- return None
- assert 'events' in json_dict
- events = [Event(e) for e in json_dict['events']]
- tracing_track = TracingTrack(None, clovis_constants.DEFAULT_CATEGORIES)
- tracing_track._categories = set(json_dict.get('categories', []))
- tracing_track._events = events
- tracing_track._base_msec = events[0].start_msec if events else 0
- for e in events[1:]:
- if e.type == 'M':
- continue # No timestamp for metadata events.
- assert e.start_msec > 0
- if e.start_msec < tracing_track._base_msec:
- tracing_track._base_msec = e.start_msec
- return tracing_track
-
- def OverlappingEvents(self, start_msec, end_msec):
- self._IndexEvents()
- return self._interval_tree.OverlappingEvents(start_msec, end_msec)
-
- def EventsEndingBetween(self, start_msec, end_msec):
- """Gets the list of events ending within an interval.
-
- Args:
- start_msec: the start of the range to query, in milliseconds, inclusive.
- end_msec: the end of the range to query, in milliseconds, inclusive.
-
- Returns:
- See OverlappingEvents() above.
- """
- overlapping_events = self.OverlappingEvents(start_msec, end_msec)
- return [e for e in overlapping_events
- if start_msec <= e.end_msec <= end_msec]
-
- def EventFromStep(self, step_event):
- """Returns the Event associated with a step event, or None.
-
- Args:
- step_event: (Event) Step event.
-
- Returns:
- an Event that matches the step event, or None.
- """
- self._IndexEvents()
- assert 'step' in step_event.args and step_event.tracing_event['ph'] == 'T'
- candidates = self._interval_tree.EventsAt(step_event.start_msec)
- for event in candidates:
- # IDs are only unique within a process (often they are pointers).
- if (event.pid == step_event.pid and event.tracing_event['ph'] != 'T'
- and event.name == step_event.name and event.id == step_event.id):
- return event
- return None
-
- def _IndexEvents(self, strict=False):
- if self._interval_tree:
- return
- complete_events = []
- spanning_events = self._SpanningEvents()
- for event in self._events:
- if not event.IsIndexable():
- continue
- if event.IsComplete():
- complete_events.append(event)
- continue
- matched_event = spanning_events.Match(event, strict)
- if matched_event is not None:
- complete_events.append(matched_event)
- self._interval_tree = _IntervalTree.FromEvents(complete_events)
-
- if strict and spanning_events.HasPending():
- raise devtools_monitor.DevToolsConnectionException(
- 'Pending spanning events: %s' %
- '\n'.join([str(e) for e in spanning_events.PendingEvents()]))
-
- def _GetEvents(self):
- self._IndexEvents()
- return self._interval_tree.GetEvents()
-
- def HasLoadingSucceeded(self):
- """Returns whether the loading has succeed at recording time."""
- main_frame_id = self.GetMainFrameRoutingID()
- for event in self.GetMatchingEvents(
- 'navigation', 'RenderFrameImpl::didFailProvisionalLoad'):
- if event.args['id'] == main_frame_id:
- return False
- for event in self.GetMatchingEvents(
- 'navigation', 'RenderFrameImpl::didFailLoad'):
- if event.args['id'] == main_frame_id:
- return False
- return True
-
- class _SpanningEvents(object):
- def __init__(self):
- self._duration_stack = []
- self._async_stacks = {}
- self._objects = {}
- self._MATCH_HANDLER = {
- 'B': self._DurationBegin,
- 'E': self._DurationEnd,
- 'b': self._AsyncStart,
- 'e': self._AsyncEnd,
- 'S': self._AsyncStart,
- 'F': self._AsyncEnd,
- 'N': self._ObjectCreated,
- 'D': self._ObjectDestroyed,
- 'M': self._Ignore,
- 'X': self._Ignore,
- 'R': self._Ignore,
- 'p': self._Ignore,
- '(': self._Ignore, # Context events.
- ')': self._Ignore, # Ditto.
- None: self._Ignore,
- }
-
- def Match(self, event, strict=False):
- return self._MATCH_HANDLER.get(
- event.type, self._Unsupported)(event, strict)
-
- def HasPending(self):
- return (self._duration_stack or
- self._async_stacks or
- self._objects)
-
- def PendingEvents(self):
- return itertools.chain(
- (e for e in self._duration_stack),
- (o for o in self._objects),
- itertools.chain.from_iterable((
- (e for e in s) for s in self._async_stacks.itervalues())))
-
- def _AsyncKey(self, event, _):
- return (event.tracing_event['cat'], event.id)
-
- def _Ignore(self, _event, _):
- return None
-
- def _Unsupported(self, event, _):
- raise devtools_monitor.DevToolsConnectionException(
- 'Unsupported spanning event type: %s' % event)
-
- def _DurationBegin(self, event, _):
- self._duration_stack.append(event)
- return None
-
- def _DurationEnd(self, event, _):
- if not self._duration_stack:
- raise devtools_monitor.DevToolsConnectionException(
- 'Unmatched duration end: %s' % event)
- start = self._duration_stack.pop()
- start.SetClose(event)
- return start
-
- def _AsyncStart(self, event, strict):
- key = self._AsyncKey(event, strict)
- self._async_stacks.setdefault(key, []).append(event)
- return None
-
- def _AsyncEnd(self, event, strict):
- key = self._AsyncKey(event, strict)
- if key not in self._async_stacks:
- message = 'Unmatched async end %s: %s' % (key, event)
- if strict:
- raise devtools_monitor.DevToolsConnectionException(message)
- else:
- logging.warning(message)
- return None
- stack = self._async_stacks[key]
- start = stack.pop()
- if not stack:
- del self._async_stacks[key]
- start.SetClose(event)
- return start
-
- def _ObjectCreated(self, event, _):
- # The tracing event format has object deletion timestamps being exclusive,
- # that is the timestamp for a deletion my equal that of the next create at
- # the same address. This asserts that does not happen in practice as it is
- # inconvenient to handle that correctly here.
- if event.id in self._objects:
- raise devtools_monitor.DevToolsConnectionException(
- 'Multiple objects at same address: %s, %s' %
- (event, self._objects[event.id]))
- self._objects[event.id] = event
- return None
-
- def _ObjectDestroyed(self, event, _):
- if event.id not in self._objects:
- raise devtools_monitor.DevToolsConnectionException(
- 'Missing object creation for %s' % event)
- start = self._objects[event.id]
- del self._objects[event.id]
- start.SetClose(event)
- return start
-
-
-class Event(object):
- """Wraps a tracing event."""
- CLOSING_EVENTS = {'E': 'B',
- 'e': 'b',
- 'F': 'S',
- 'D': 'N'}
- __slots__ = ('_tracing_event', 'start_msec', 'end_msec', '_synthetic')
- def __init__(self, tracing_event, synthetic=False):
- """Creates Event.
-
- Intended to be created only by TracingTrack.
-
- Args:
- tracing_event: JSON tracing event, as defined in https://goo.gl/Qabkqk.
- synthetic: True if the event is synthetic. This is only used for indexing
- internal to TracingTrack.
- """
- if not synthetic and tracing_event['ph'] in ['s', 't', 'f']:
- raise devtools_monitor.DevToolsConnectionException(
- 'Unsupported event: %s' % tracing_event)
-
- self._tracing_event = tracing_event
- # Note tracing event times are in microseconds.
- self.start_msec = tracing_event['ts'] / 1000.0
- self.end_msec = None
- self._synthetic = synthetic
- if self.type == 'X':
- # Some events don't have a duration.
- duration = (tracing_event['dur']
- if 'dur' in tracing_event else tracing_event['tdur'])
- self.end_msec = self.start_msec + duration / 1000.0
-
- @property
- def type(self):
- if self._synthetic:
- return None
- return self._tracing_event['ph']
-
- @property
- def category(self):
- return self._tracing_event['cat']
-
- @property
- def pid(self):
- return self._tracing_event['pid']
-
- @property
- def args(self):
- return self._tracing_event.get('args', {})
-
- @property
- def id(self):
- return self._tracing_event.get('id')
-
- @property
- def name(self):
- return self._tracing_event['name']
-
- @property
- def tracing_event(self):
- return self._tracing_event
-
- @property
- def synthetic(self):
- return self._synthetic
-
- def __str__(self):
- return ''.join([str(self._tracing_event),
- '[%s,%s]' % (self.start_msec, self.end_msec)])
-
- def Matches(self, category, name):
- """Match tracing events.
-
- Args:
- category: a tracing category (event['cat']).
- name: the tracing event name (event['name']).
-
- Returns:
- True if the event matches and False otherwise.
- """
- if name != self.name:
- return False
- categories = self.category.split(',')
- return category in categories
-
- def IsIndexable(self):
- """True iff the event can be indexed by time."""
- return self._synthetic or self.type not in [
- 'I', 'P', 'c', 'C',
- 'n', 'T', 'p', # TODO(mattcary): ?? instant types of async events.
- 'O', # TODO(mattcary): ?? object snapshot
- 'M' # Metadata
- ]
-
- def IsComplete(self):
- return self.type == 'X'
-
- def Synthesize(self):
- """Expand into synthetic events.
-
- Returns:
- A list of events, possibly some synthetic, whose start times are all
- interesting for purposes of indexing. If the event is not indexable the
- set may be empty.
- """
- if not self.IsIndexable():
- return []
- if self.IsComplete():
- # Tracing event timestamps are microseconds!
- return [self, Event({'ts': self.end_msec * 1000}, synthetic=True)]
- return [self]
-
- def SetClose(self, closing):
- """Close a spanning event.
-
- Args:
- closing: The closing event.
-
- Raises:
- devtools_monitor.DevToolsConnectionException if closing can't property
- close this event.
- """
- if self.type != self.CLOSING_EVENTS.get(closing.type):
- raise devtools_monitor.DevToolsConnectionException(
- 'Bad closing: %s --> %s' % (self, closing))
- if self.type in ['b', 'S'] and (
- self.tracing_event['cat'] != closing.tracing_event['cat'] or
- self.id != closing.id):
- raise devtools_monitor.DevToolsConnectionException(
- 'Bad async closing: %s --> %s' % (self, closing))
- self.end_msec = closing.start_msec
- if 'args' in closing.tracing_event:
- self.tracing_event.setdefault(
- 'args', {}).update(closing.tracing_event['args'])
-
- def ToJsonDict(self):
- return self._tracing_event
-
- @classmethod
- def FromJsonDict(cls, json_dict):
- return Event(json_dict)
-
-
-class _IntervalTree(object):
- """Simple interval tree. This is not an optimal one, as the split is done with
- an equal number of events on each side, according to start time.
- """
- _TRESHOLD = 100
- def __init__(self, start, end, events):
- """Builds an interval tree.
-
- Args:
- start: start timestamp of this node, in ms.
- end: end timestamp covered by this node, in ms.
- events: Iterable of objects having start_msec and end_msec fields. Has to
- be sorted by start_msec.
- """
- self.start = start
- self.end = end
- self._events = events
- self._left = self._right = None
- if len(self._events) > self._TRESHOLD:
- self._Divide()
-
- @classmethod
- def FromEvents(cls, events):
- """Returns an IntervalTree instance from a list of events."""
- filtered_events = [e for e in events
- if e.start_msec is not None and e.end_msec is not None]
- filtered_events.sort(key=operator.attrgetter('start_msec'))
- start = min(event.start_msec for event in filtered_events)
- end = max(event.end_msec for event in filtered_events)
- return _IntervalTree(start, end, filtered_events)
-
- def OverlappingEvents(self, start, end):
- """Returns a set of events overlapping with [start, end)."""
- if min(end, self.end) - max(start, self.start) <= 0:
- return set()
- elif self._IsLeaf():
- result = set()
- for event in self._events:
- if self._Overlaps(event, start, end):
- result.add(event)
- return result
- else:
- return (self._left.OverlappingEvents(start, end)
- | self._right.OverlappingEvents(start, end))
-
- def EventsAt(self, timestamp):
- result = set()
- if self._IsLeaf():
- for event in self._events:
- if event.start_msec <= timestamp < event.end_msec:
- result.add(event)
- else:
- if self._left.start <= timestamp < self._left.end:
- result |= self._left.EventsAt(timestamp)
- if self._right.start <= timestamp < self._right.end:
- result |= self._right.EventsAt(timestamp)
- return result
-
- def GetEvents(self):
- return self._events
-
- def _Divide(self):
- middle = len(self._events) / 2
- left_events = self._events[:middle]
- right_events = self._events[middle:]
- left_end = max(e.end_msec for e in left_events)
- right_start = min(e.start_msec for e in right_events)
- self._left = _IntervalTree(self.start, left_end, left_events)
- self._right = _IntervalTree(right_start, self.end, right_events)
-
- def _IsLeaf(self):
- return self._left is None
-
- @classmethod
- def _Overlaps(cls, event, start, end):
- return (min(end, event.end_msec) - max(start, event.start_msec) > 0
- or start <= event.start_msec < end) # For instant events.
« no previous file with comments | « tools/android/loading/test_utils.py ('k') | tools/android/loading/tracing_track.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698