Upstream version 6.35.121.0
[platform/framework/web/crosswalk.git] / src / tools / telemetry / telemetry / core / backends / chrome / tracing_backend.py
1 # Copyright 2013 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
5 import json
6 import logging
7 import re
8 import socket
9 import threading
10 import weakref
11
12 from telemetry.core.backends.chrome import tracing_timeline_data
13 from telemetry.core.backends.chrome import websocket
14 from telemetry.core.backends.chrome import websocket_browser_connection
15
16
17 class TracingUnsupportedException(Exception):
18   pass
19
20
21 class CategoryFilter(object):
22   def __init__(self, filter_string):
23     self.excluded = set()
24     self.included = set()
25     self.disabled = set()
26     self.synthetic_delays = set()
27     self.contains_wildcards = False
28
29     if not filter_string:
30       return
31
32     if '*' in filter_string or '?' in filter_string:
33       self.contains_wildcards = True
34
35     filter_set = set(filter_string.split(','))
36     delay_re = re.compile(r'DELAY[(][A-Za-z0-9._;]+[)]')
37     for category in filter_set:
38       if category == '':
39         continue
40       if delay_re.match(category):
41         self.synthetic_delays.add(category)
42       elif category[0] == '-':
43         category = category[1:]
44         self.excluded.add(category)
45       elif category.startswith('disabled-by-default-'):
46         self.disabled.add(category)
47       else:
48         self.included.add(category)
49
50   def IsSubset(self, other):
51     """ Determine if filter A (self) is a subset of filter B (other).
52         Returns True if A is a subset of B, False if A is not a subset of B,
53         and None if we can't tell for sure.
54     """
55     # We don't handle filters with wildcards in this test.
56     if self.contains_wildcards or other.contains_wildcards:
57       return None
58
59     # Disabled categories get into a trace if and only if they are contained in
60     # the 'disabled' set. Return False if A's disabled set is not a subset of
61     # B's disabled set.
62     if not self.disabled <= other.disabled:
63       return False
64
65     # If A defines more or different synthetic delays than B, then A is not a
66     # subset.
67     if not self.synthetic_delays <= other.synthetic_delays:
68       return False
69
70     if self.included and other.included:
71       # A and B have explicit include lists. If A includes something that B
72       # doesn't, return False.
73       if not self.included <= other.included:
74         return False
75     elif self.included:
76       # Only A has an explicit include list. If A includes something that B
77       # excludes, return False.
78       if self.included.intersection(other.excluded):
79         return False
80     elif other.included:
81       # Only B has an explicit include list. We don't know which categories are
82       # contained in the default list, so return None.
83       return None
84     else:
85       # None of the filter have explicit include list. If B excludes categories
86       # that A doesn't exclude, return False.
87       if not other.excluded <= self.excluded:
88         return False
89
90     return True
91
92 class TracingBackend(object):
93   def __init__(self, devtools_port):
94     self._conn = websocket_browser_connection.WebSocketBrowserConnection(
95         devtools_port)
96     self._thread = None
97     self._category_filter = None
98     self._nesting = 0
99     self._tracing_data = []
100     # Use a WeakKeyDictionary, because an ordinary dictionary could keep
101     # references to Tab objects around until it gets garbage collected.
102     # This would prevent telemetry from navigating to another page.
103     self._tab_to_marker_mapping = weakref.WeakKeyDictionary()
104
105   @property
106   def is_tracing_running(self):
107     return self._thread != None
108
109   def AddTabToMarkerMapping(self, tab, marker):
110     self._tab_to_marker_mapping[tab] = marker
111
112   def StartTracing(self, custom_categories=None, timeout=10):
113     """ Starts tracing on the first nested call and returns True. Returns False
114         and does nothing on subsequent nested calls.
115     """
116     self._nesting += 1
117     if self.is_tracing_running:
118       new_category_filter = CategoryFilter(custom_categories)
119       is_subset = new_category_filter.IsSubset(self._category_filter)
120       assert(is_subset != False)
121       if is_subset == None:
122         logging.warning('Cannot determine if category filter of nested ' +
123                         'StartTracing call is subset of current filter.')
124       return False
125     self._CheckNotificationSupported()
126     req = {'method': 'Tracing.start'}
127     self._category_filter = CategoryFilter(custom_categories)
128     if custom_categories:
129       req['params'] = {'categories': custom_categories}
130     self._conn.SyncRequest(req, timeout)
131     # Tracing.start will send asynchronous notifications containing trace
132     # data, until Tracing.end is called.
133     self._thread = threading.Thread(target=self._TracingReader)
134     self._thread.daemon = True
135     self._thread.start()
136     return True
137
138   def StopTracing(self):
139     """ Stops tracing on the innermost (!) nested call, because we cannot get
140         results otherwise. Resets _tracing_data on the outermost nested call.
141         Returns the result of the trace, as TracingTimelineData object.
142     """
143     self._nesting -= 1
144     assert self._nesting >= 0
145     if self.is_tracing_running:
146       req = {'method': 'Tracing.end'}
147       self._conn.SendRequest(req)
148       self._thread.join(timeout=30)
149       if self._thread.is_alive():
150         raise RuntimeError('Timed out waiting for tracing thread to join.')
151       self._thread = None
152     if self._nesting == 0:
153       self._category_filter = None
154       return self._GetTraceResultAndReset()
155     else:
156       return self._GetTraceResult()
157
158   def _GetTraceResult(self):
159     assert not self.is_tracing_running
160     return tracing_timeline_data.TracingTimelineData(
161         self._tracing_data, self._tab_to_marker_mapping)
162
163   def _GetTraceResultAndReset(self):
164     result = self._GetTraceResult()
165     # Reset tab to marker mapping for the next tracing run. Don't use clear(),
166     # because a TraceResult may still hold a reference to the dictionary object.
167     self._tab_to_marker_mapping = weakref.WeakKeyDictionary()
168     self._tracing_data = []
169     return result
170
171   def _TracingReader(self):
172     while self._conn.socket:
173       try:
174         data = self._conn.socket.recv()
175         if not data:
176           break
177         res = json.loads(data)
178         logging.debug('got [%s]', data)
179         if 'Tracing.dataCollected' == res.get('method'):
180           value = res.get('params', {}).get('value')
181           if type(value) in [str, unicode]:
182             self._tracing_data.append(value)
183           elif type(value) is list:
184             self._tracing_data.extend(value)
185           else:
186             logging.warning('Unexpected type in tracing data')
187         elif 'Tracing.tracingComplete' == res.get('method'):
188           break
189       except websocket.WebSocketTimeoutException as e:
190         logging.warning('Unusual timeout waiting for tracing response (%s).',
191                         e)
192       except (socket.error, websocket.WebSocketException) as e:
193         logging.warning('Unrecoverable exception when reading tracing response '
194                         '(%s, %s).', type(e), e)
195         raise
196
197   def Close(self):
198     self._conn.Close()
199
200   def _CheckNotificationSupported(self):
201     """Ensures we're running against a compatible version of chrome."""
202     req = {'method': 'Tracing.hasCompleted'}
203     res = self._conn.SyncRequest(req)
204     if res.get('response'):
205       raise TracingUnsupportedException(
206           'Tracing not supported for this browser')
207     elif 'error' in res:
208       return