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 5import logging 6 7from telemetry.core.backends.chrome import inspector_websocket 8from telemetry.core.platform import tracing_category_filter 9from telemetry.core.platform import tracing_options 10 11 12class TracingUnsupportedException(Exception): 13 pass 14 15 16class TracingTimeoutException(Exception): 17 pass 18 19 20class TracingBackend(object): 21 def __init__(self, devtools_port, chrome_browser_backend): 22 self._inspector_websocket = inspector_websocket.InspectorWebsocket( 23 self._NotificationHandler, 24 self._ErrorHandler) 25 26 self._inspector_websocket.Connect( 27 'ws://127.0.0.1:%i/devtools/browser' % devtools_port) 28 self._category_filter = None 29 self._nesting = 0 30 self._tracing_data = [] 31 self._is_tracing_running = False 32 self._chrome_browser_backend = chrome_browser_backend 33 34 @property 35 def is_tracing_running(self): 36 return self._is_tracing_running 37 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. 41 """ 42 self._nesting += 1 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) 48 if is_subset == None: 49 logging.warning('Cannot determine if category filter of nested ' + 50 'StartTracing call is subset of current filter.') 51 return False 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): 57 logging.warning( 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'} 65 req['params'] = {} 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) 72 if custom_categories: 73 req['params']['categories'] = custom_categories 74 self._inspector_websocket.SyncRequest(req, timeout) 75 self._is_tracing_running = True 76 return True 77 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. 82 """ 83 self._nesting -= 1 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. 91 try: 92 self._inspector_websocket.DispatchNotificationsUntilDone(timeout) 93 except \ 94 inspector_websocket.DispatchNotificationsUntilDoneTimeoutException \ 95 as err: 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() 104 else: 105 return self._GetTraceResult() 106 107 def _GetTraceResult(self): 108 assert not self.is_tracing_running 109 return self._tracing_data 110 111 def _GetTraceResultAndReset(self): 112 result = self._GetTraceResult() 113 114 self._tracing_data = [] 115 return result 116 117 def _ErrorHandler(self, elapsed): 118 logging.error('Unrecoverable error after %ds reading tracing response.', 119 elapsed) 120 raise 121 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) 129 else: 130 logging.warning('Unexpected type in tracing data') 131 elif 'Tracing.tracingComplete' == res.get('method'): 132 return True 133 134 def Close(self): 135 self._inspector_websocket.Disconnect() 136 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') 144 elif 'error' in res: 145 return 146