1# Copyright 2014 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 ast 6import atexit 7import contextlib 8import gc 9import logging 10import os 11import sys 12import tempfile 13import traceback 14import uuid 15 16from py_trace_event import trace_event 17from telemetry.core import discover 18from telemetry.core import util 19from telemetry.internal.platform import tracing_agent 20from telemetry.internal.platform.tracing_agent import chrome_tracing_agent 21from telemetry.timeline import trace_data as trace_data_module 22from telemetry.timeline import tracing_config 23 24 25def _IterAllTracingAgentClasses(): 26 tracing_agent_dir = os.path.join( 27 os.path.dirname(os.path.realpath(__file__)), 'tracing_agent') 28 return discover.DiscoverClasses( 29 tracing_agent_dir, util.GetTelemetryDir(), 30 tracing_agent.TracingAgent).itervalues() 31 32 33class TracingControllerStoppedError(Exception): 34 pass 35 36 37class _TracingState(object): 38 39 def __init__(self, config, timeout): 40 self._builder = trace_data_module.TraceDataBuilder() 41 self._config = config 42 self._timeout = timeout 43 44 @property 45 def builder(self): 46 return self._builder 47 48 @property 49 def config(self): 50 return self._config 51 52 @property 53 def timeout(self): 54 return self._timeout 55 56 57class TracingControllerBackend(object): 58 def __init__(self, platform_backend): 59 self._platform_backend = platform_backend 60 self._current_state = None 61 self._supported_agents_classes = [ 62 agent_classes for agent_classes in _IterAllTracingAgentClasses() if 63 agent_classes.IsSupported(platform_backend)] 64 self._active_agents_instances = [] 65 self._trace_log = None 66 self._is_tracing_controllable = True 67 68 def StartTracing(self, config, timeout): 69 if self.is_tracing_running: 70 return False 71 72 assert isinstance(config, tracing_config.TracingConfig) 73 assert len(self._active_agents_instances) == 0 74 75 self._current_state = _TracingState(config, timeout) 76 # Hack: chrome tracing agent may only depend on the number of alive chrome 77 # devtools processes, rather platform (when startup tracing is not 78 # supported), hence we add it to the list of supported agents here if it was 79 # not added. 80 if (chrome_tracing_agent.ChromeTracingAgent.IsSupported( 81 self._platform_backend) and 82 not chrome_tracing_agent.ChromeTracingAgent in 83 self._supported_agents_classes): 84 self._supported_agents_classes.append( 85 chrome_tracing_agent.ChromeTracingAgent) 86 87 self.StartAgentTracing(config, timeout) 88 for agent_class in self._supported_agents_classes: 89 agent = agent_class(self._platform_backend) 90 if agent.StartAgentTracing(config, timeout): 91 self._active_agents_instances.append(agent) 92 return True 93 94 def _GenerateClockSyncId(self): 95 return str(uuid.uuid4()) 96 97 @contextlib.contextmanager 98 def _DisableGarbageCollection(self): 99 try: 100 gc.disable() 101 yield 102 finally: 103 gc.enable() 104 105 def StopTracing(self): 106 assert self.is_tracing_running, 'Can only stop tracing when tracing is on.' 107 self._IssueClockSyncMarker() 108 builder = self._current_state.builder 109 110 raised_exception_messages = [] 111 for agent in self._active_agents_instances + [self]: 112 try: 113 agent.StopAgentTracing(builder) 114 except Exception: # pylint: disable=broad-except 115 raised_exception_messages.append( 116 ''.join(traceback.format_exception(*sys.exc_info()))) 117 118 self._active_agents_instances = [] 119 self._current_state = None 120 121 if raised_exception_messages: 122 raise TracingControllerStoppedError( 123 'Exceptions raised when trying to stop tracing:\n' + 124 '\n'.join(raised_exception_messages)) 125 126 return builder.AsData() 127 128 def FlushTracing(self): 129 assert self.is_tracing_running, 'Can only flush tracing when tracing is on.' 130 self._IssueClockSyncMarker() 131 132 raised_exception_messages = [] 133 # Flushing the controller's pytrace is not supported. 134 for agent in self._active_agents_instances: 135 try: 136 if agent.SupportsFlushingAgentTracing(): 137 agent.FlushAgentTracing(self._current_state.config, 138 self._current_state.timeout, 139 self._current_state.builder) 140 except Exception: # pylint: disable=broad-except 141 raised_exception_messages.append( 142 ''.join(traceback.format_exception(*sys.exc_info()))) 143 144 if raised_exception_messages: 145 raise TracingControllerStoppedError( 146 'Exceptions raised when trying to stop tracing:\n' + 147 '\n'.join(raised_exception_messages)) 148 149 def StartAgentTracing(self, config, timeout): 150 self._is_tracing_controllable = self._IsTracingControllable() 151 if not self._is_tracing_controllable: 152 return False 153 154 tf = tempfile.NamedTemporaryFile(delete=False) 155 self._trace_log = tf.name 156 tf.close() 157 del config # unused 158 del timeout # unused 159 assert not trace_event.trace_is_enabled(), 'Tracing already running.' 160 trace_event.trace_enable(self._trace_log) 161 assert trace_event.trace_is_enabled(), 'Tracing didn\'t enable properly.' 162 return True 163 164 def StopAgentTracing(self, trace_data_builder): 165 if not self._is_tracing_controllable: 166 return 167 assert trace_event.trace_is_enabled(), 'Tracing not running' 168 trace_event.trace_disable() 169 assert not trace_event.trace_is_enabled(), 'Tracing didnt disable properly.' 170 with open(self._trace_log, 'r') as fp: 171 data = ast.literal_eval(fp.read() + ']') 172 trace_data_builder.AddEventsTo(trace_data_module.TELEMETRY_PART, data) 173 try: 174 os.remove(self._trace_log) 175 self._trace_log = None 176 except OSError: 177 logging.exception('Error when deleting %s, will try again at exit.', 178 self._trace_log) 179 def DeleteAtExit(path): 180 os.remove(path) 181 atexit.register(DeleteAtExit, self._trace_log) 182 self._trace_log = None 183 184 def SupportsExplicitClockSync(self): 185 return True 186 187 def _RecordIssuerClockSyncMarker(self, sync_id, issue_ts): 188 """ Record clock sync event. 189 190 Args: 191 sync_id: Unqiue id for sync event. 192 issue_ts: timestamp before issuing clocksync to agent. 193 """ 194 if self._is_tracing_controllable: 195 trace_event.clock_sync(sync_id, issue_ts=issue_ts) 196 197 def _IssueClockSyncMarker(self): 198 with self._DisableGarbageCollection(): 199 for agent in self._active_agents_instances: 200 if agent.SupportsExplicitClockSync(): 201 sync_id = self._GenerateClockSyncId() 202 agent.RecordClockSyncMarker(sync_id, 203 self._RecordIssuerClockSyncMarker) 204 205 def IsChromeTracingSupported(self): 206 return chrome_tracing_agent.ChromeTracingAgent.IsSupported( 207 self._platform_backend) 208 209 @property 210 def is_tracing_running(self): 211 return self._current_state is not None 212 213 def _GetActiveChromeTracingAgent(self): 214 if not self.is_tracing_running: 215 return None 216 if not self._current_state.config.enable_chrome_trace: 217 return None 218 for agent in self._active_agents_instances: 219 if isinstance(agent, chrome_tracing_agent.ChromeTracingAgent): 220 return agent 221 return None 222 223 def GetChromeTraceConfig(self): 224 agent = self._GetActiveChromeTracingAgent() 225 if agent: 226 return agent.trace_config 227 return None 228 229 def GetChromeTraceConfigFile(self): 230 agent = self._GetActiveChromeTracingAgent() 231 if agent: 232 return agent.trace_config_file 233 return None 234 235 def _IsTracingControllable(self): 236 return trace_event.is_tracing_controllable() 237 238 def ClearStateIfNeeded(self): 239 chrome_tracing_agent.ClearStarupTracingStateIfNeeded(self._platform_backend) 240