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.
7 from telemetry.core.backends.chrome import inspector_websocket
8 from telemetry.core.platform import tracing_category_filter
9 from telemetry.core.platform import tracing_options
12 class TracingUnsupportedException(Exception):
16 class TracingTimeoutException(Exception):
20 class TracingBackend(object):
21 def __init__(self, devtools_port, chrome_browser_backend):
22 self._inspector_websocket = inspector_websocket.InspectorWebsocket(
23 self._NotificationHandler,
26 self._inspector_websocket.Connect(
27 'ws://127.0.0.1:%i/devtools/browser' % devtools_port)
28 self._category_filter = None
30 self._tracing_data = []
31 self._is_tracing_running = False
32 self._chrome_browser_backend = chrome_browser_backend
35 def is_tracing_running(self):
36 return self._is_tracing_running
38 def StartTracing(self, trace_options, custom_categories=None, timeout=10):
39 """ Starts tracing on the first nested call and returns True. Returns False
40 and does nothing on subsequent nested calls.
43 if self.is_tracing_running:
44 new_category_filter = tracing_category_filter.TracingCategoryFilter(
45 filter_string=custom_categories)
46 is_subset = new_category_filter.IsSubset(self._category_filter)
47 assert(is_subset != False)
49 logging.warning('Cannot determine if category filter of nested ' +
50 'StartTracing call is subset of current filter.')
52 self._CheckNotificationSupported()
53 #TODO(nednguyen): remove this when the stable branch pass 2118.
54 if (trace_options.record_mode == tracing_options.RECORD_AS_MUCH_AS_POSSIBLE
55 and self._chrome_browser_backend.chrome_branch_number
56 and self._chrome_browser_backend.chrome_branch_number < 2118):
58 'Cannot use %s tracing mode on chrome browser with branch version %i,'
59 ' (<2118) fallback to use %s tracing mode' % (
60 trace_options.record_mode,
61 self._chrome_browser_backend.chrome_branch_number,
62 tracing_options.RECORD_UNTIL_FULL))
63 trace_options.record_mode = tracing_options.RECORD_UNTIL_FULL
64 req = {'method': 'Tracing.start'}
66 m = {tracing_options.RECORD_UNTIL_FULL: 'record-until-full',
67 tracing_options.RECORD_AS_MUCH_AS_POSSIBLE:
68 'record-as-much-as-possible'}
69 req['params']['options'] = m[trace_options.record_mode]
70 self._category_filter = tracing_category_filter.TracingCategoryFilter(
71 filter_string=custom_categories)
73 req['params']['categories'] = custom_categories
74 self._inspector_websocket.SyncRequest(req, timeout)
75 self._is_tracing_running = True
78 def StopTracing(self, timeout=30):
79 """ Stops tracing on the innermost (!) nested call, because we cannot get
80 results otherwise. Resets _tracing_data on the outermost nested call.
81 Returns the result of the trace, as TracingTimelineData object.
84 assert self._nesting >= 0
85 if self.is_tracing_running:
86 req = {'method': 'Tracing.end'}
87 self._inspector_websocket.SendAndIgnoreResponse(req)
88 # After Tracing.end, chrome browser will send asynchronous notifications
89 # containing trace data. This is until Tracing.tracingComplete is sent,
90 # which means there is no trace buffers pending flush.
92 self._inspector_websocket.DispatchNotificationsUntilDone(timeout)
94 inspector_websocket.DispatchNotificationsUntilDoneTimeoutException \
96 raise TracingTimeoutException(
97 'Trace data was not fully received due to timeout after %s '
98 'seconds. If the trace data is big, you may want to increase the '
99 'time out amount.' % err.elapsed_time)
100 self._is_tracing_running = False
101 if self._nesting == 0:
102 self._category_filter = None
103 return self._GetTraceResultAndReset()
105 return self._GetTraceResult()
107 def _GetTraceResult(self):
108 assert not self.is_tracing_running
109 return self._tracing_data
111 def _GetTraceResultAndReset(self):
112 result = self._GetTraceResult()
114 self._tracing_data = []
117 def _ErrorHandler(self, elapsed):
118 logging.error('Unrecoverable error after %ds reading tracing response.',
122 def _NotificationHandler(self, res):
123 if 'Tracing.dataCollected' == res.get('method'):
124 value = res.get('params', {}).get('value')
125 if type(value) in [str, unicode]:
126 self._tracing_data.append(value)
127 elif type(value) is list:
128 self._tracing_data.extend(value)
130 logging.warning('Unexpected type in tracing data')
131 elif 'Tracing.tracingComplete' == res.get('method'):
135 self._inspector_websocket.Disconnect()
137 def _CheckNotificationSupported(self):
138 """Ensures we're running against a compatible version of chrome."""
139 req = {'method': 'Tracing.hasCompleted'}
140 res = self._inspector_websocket.SyncRequest(req)
141 if res.get('response'):
142 raise TracingUnsupportedException(
143 'Tracing not supported for this browser')