1# Copyright 2015 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 atexit 6import logging 7import os 8import shutil 9import stat 10import sys 11import tempfile 12import traceback 13 14from telemetry.internal.platform import tracing_agent 15from telemetry.internal.platform.tracing_agent import ( 16 chrome_tracing_devtools_manager) 17 18_DESKTOP_OS_NAMES = ['linux', 'mac', 'win'] 19_STARTUP_TRACING_OS_NAMES = _DESKTOP_OS_NAMES + ['android'] 20 21# The trace config file path should be the same as specified in 22# src/components/tracing/trace_config_file.[h|cc] 23_CHROME_TRACE_CONFIG_DIR_ANDROID = '/data/local/' 24_CHROME_TRACE_CONFIG_FILE_NAME = 'chrome-trace-config.json' 25 26 27def ClearStarupTracingStateIfNeeded(platform_backend): 28 # Trace config file has fixed path on Android and temporary path on desktop. 29 if platform_backend.GetOSName() == 'android': 30 trace_config_file = os.path.join(_CHROME_TRACE_CONFIG_DIR_ANDROID, 31 _CHROME_TRACE_CONFIG_FILE_NAME) 32 platform_backend.device.RunShellCommand( 33 ['rm', '-f', trace_config_file], check_return=True, as_root=True) 34 35 36class ChromeTracingStartedError(Exception): 37 pass 38 39 40class ChromeTracingStoppedError(Exception): 41 pass 42 43 44class ChromeTracingAgent(tracing_agent.TracingAgent): 45 def __init__(self, platform_backend): 46 super(ChromeTracingAgent, self).__init__(platform_backend) 47 self._trace_config = None 48 self._trace_config_file = None 49 50 @property 51 def trace_config(self): 52 # Trace config is also used to check if Chrome tracing is running or not. 53 return self._trace_config 54 55 @property 56 def trace_config_file(self): 57 return self._trace_config_file 58 59 @classmethod 60 def IsStartupTracingSupported(cls, platform_backend): 61 if platform_backend.GetOSName() in _STARTUP_TRACING_OS_NAMES: 62 return True 63 else: 64 return False 65 66 @classmethod 67 def IsSupported(cls, platform_backend): 68 if cls.IsStartupTracingSupported(platform_backend): 69 return True 70 else: 71 return chrome_tracing_devtools_manager.IsSupported(platform_backend) 72 73 def _StartStartupTracing(self, config): 74 if not self.IsStartupTracingSupported(self._platform_backend): 75 return False 76 self._CreateTraceConfigFile(config) 77 return True 78 79 def _StartDevToolsTracing(self, config, timeout): 80 if not chrome_tracing_devtools_manager.IsSupported(self._platform_backend): 81 return False 82 devtools_clients = (chrome_tracing_devtools_manager 83 .GetActiveDevToolsClients(self._platform_backend)) 84 if not devtools_clients: 85 return False 86 for client in devtools_clients: 87 if client.is_tracing_running: 88 raise ChromeTracingStartedError( 89 'Tracing is already running on devtools at port %s on platform' 90 'backend %s.' % (client.remote_port, self._platform_backend)) 91 client.StartChromeTracing( 92 config, config.tracing_category_filter.filter_string, timeout) 93 return True 94 95 def StartAgentTracing(self, config, timeout): 96 if not config.enable_chrome_trace: 97 return False 98 99 if self._trace_config: 100 raise ChromeTracingStartedError( 101 'Tracing is already running on platform backend %s.' 102 % self._platform_backend) 103 104 if (config.enable_android_graphics_memtrack and 105 self._platform_backend.GetOSName() == 'android'): 106 self._platform_backend.SetGraphicsMemoryTrackingEnabled(True) 107 108 # Chrome tracing Agent needs to start tracing for chrome browsers that are 109 # not yet started, and for the ones that already are. For the former, we 110 # first setup the trace_config_file, which allows browsers that starts after 111 # this point to use it for enabling tracing upon browser startup. For the 112 # latter, we invoke start tracing command through devtools for browsers that 113 # are already started and tracked by chrome_tracing_devtools_manager. 114 started_startup_tracing = self._StartStartupTracing(config) 115 started_devtools_tracing = self._StartDevToolsTracing(config, timeout) 116 if started_startup_tracing or started_devtools_tracing: 117 self._trace_config = config 118 return True 119 return False 120 121 def StopAgentTracing(self, trace_data_builder): 122 if not self._trace_config: 123 raise ChromeTracingStoppedError( 124 'Tracing is not running on platform backend %s.' 125 % self._platform_backend) 126 127 if self.IsStartupTracingSupported(self._platform_backend): 128 self._RemoveTraceConfigFile() 129 130 # We get all DevTools clients including the stale ones, so that we get an 131 # exception if there is a stale client. This is because we will potentially 132 # lose data if there is a stale client. 133 devtools_clients = (chrome_tracing_devtools_manager 134 .GetDevToolsClients(self._platform_backend)) 135 raised_execption_messages = [] 136 for client in devtools_clients: 137 try: 138 client.StopChromeTracing(trace_data_builder) 139 except Exception: 140 raised_execption_messages.append( 141 'Error when trying to stop Chrome tracing on devtools at port %s:\n%s' 142 % (client.remote_port, 143 ''.join(traceback.format_exception(*sys.exc_info())))) 144 145 if (self._trace_config.enable_android_graphics_memtrack and 146 self._platform_backend.GetOSName() == 'android'): 147 self._platform_backend.SetGraphicsMemoryTrackingEnabled(False) 148 149 self._trace_config = None 150 if raised_execption_messages: 151 raise ChromeTracingStoppedError( 152 'Exceptions raised when trying to stop Chrome devtool tracing:\n' + 153 '\n'.join(raised_execption_messages)) 154 155 def _CreateTraceConfigFileString(self, config): 156 # See src/components/tracing/trace_config_file.h for the format 157 trace_config_str = config.GetChromeTraceConfigJsonString() 158 return '{"trace_config":' + trace_config_str + '}' 159 160 def _CreateTraceConfigFile(self, config): 161 assert not self._trace_config_file 162 if self._platform_backend.GetOSName() == 'android': 163 self._trace_config_file = os.path.join(_CHROME_TRACE_CONFIG_DIR_ANDROID, 164 _CHROME_TRACE_CONFIG_FILE_NAME) 165 self._platform_backend.device.WriteFile(self._trace_config_file, 166 self._CreateTraceConfigFileString(config), as_root=True) 167 # The config file has fixed path on Android. We need to ensure it is 168 # always cleaned up. 169 atexit.register(self._RemoveTraceConfigFile) 170 elif self._platform_backend.GetOSName() in _DESKTOP_OS_NAMES: 171 self._trace_config_file = os.path.join(tempfile.mkdtemp(), 172 _CHROME_TRACE_CONFIG_FILE_NAME) 173 with open(self._trace_config_file, 'w') as f: 174 trace_config_string = self._CreateTraceConfigFileString(config) 175 logging.info('Trace config file string: %s', trace_config_string) 176 f.write(trace_config_string) 177 os.chmod(self._trace_config_file, 178 os.stat(self._trace_config_file).st_mode | stat.S_IROTH) 179 else: 180 raise NotImplementedError 181 182 def _RemoveTraceConfigFile(self): 183 if not self._trace_config_file: 184 return 185 if self._platform_backend.GetOSName() == 'android': 186 self._platform_backend.device.RunShellCommand( 187 ['rm', '-f', self._trace_config_file], check_return=True, 188 as_root=True) 189 elif self._platform_backend.GetOSName() in _DESKTOP_OS_NAMES: 190 if os.path.exists(self._trace_config_file): 191 os.remove(self._trace_config_file) 192 shutil.rmtree(os.path.dirname(self._trace_config_file)) 193 else: 194 raise NotImplementedError 195 self._trace_config_file = None 196 197 def SupportsFlushingAgentTracing(self): 198 return True 199 200 def FlushAgentTracing(self, config, timeout, trace_data_builder): 201 if not self._trace_config: 202 raise ChromeTracingStoppedError( 203 'Tracing is not running on platform backend %s.' 204 % self._platform_backend) 205 206 for backend in self._IterInspectorBackends(): 207 backend.EvaluateJavaScript("console.time('flush-tracing');") 208 209 self.StopAgentTracing(trace_data_builder) 210 self.StartAgentTracing(config, timeout) 211 212 for backend in self._IterInspectorBackends(): 213 backend.EvaluateJavaScript("console.timeEnd('flush-tracing');") 214 215 def _IterInspectorBackends(self): 216 for client in chrome_tracing_devtools_manager.GetDevToolsClients( 217 self._platform_backend): 218 context_map = client.GetUpdatedInspectableContexts() 219 for context in context_map.contexts: 220 if context['type'] in ['iframe', 'page', 'webview']: 221 yield context_map.GetInspectorBackend(context['id']) 222