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 6from telemetry.internal.util import atexit_with_log 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 self._iteration_info = None 68 69 def StartTracing(self, config, timeout): 70 if self.is_tracing_running: 71 return False 72 73 assert isinstance(config, tracing_config.TracingConfig) 74 assert len(self._active_agents_instances) == 0 75 76 self._current_state = _TracingState(config, timeout) 77 # Hack: chrome tracing agent may only depend on the number of alive chrome 78 # devtools processes, rather platform (when startup tracing is not 79 # supported), hence we add it to the list of supported agents here if it was 80 # not added. 81 if (chrome_tracing_agent.ChromeTracingAgent.IsSupported( 82 self._platform_backend) and 83 not chrome_tracing_agent.ChromeTracingAgent in 84 self._supported_agents_classes): 85 self._supported_agents_classes.append( 86 chrome_tracing_agent.ChromeTracingAgent) 87 88 self.StartAgentTracing(config, timeout) 89 for agent_class in self._supported_agents_classes: 90 agent = agent_class(self._platform_backend) 91 if agent.StartAgentTracing(config, timeout): 92 self._active_agents_instances.append(agent) 93 return True 94 95 def _GenerateClockSyncId(self): 96 return str(uuid.uuid4()) 97 98 @contextlib.contextmanager 99 def _DisableGarbageCollection(self): 100 try: 101 gc.disable() 102 yield 103 finally: 104 gc.enable() 105 106 def StopTracing(self): 107 assert self.is_tracing_running, 'Can only stop tracing when tracing is on.' 108 self._IssueClockSyncMarker() 109 builder = self._current_state.builder 110 111 raised_exception_messages = [] 112 for agent in self._active_agents_instances + [self]: 113 try: 114 agent.StopAgentTracing() 115 except Exception: # pylint: disable=broad-except 116 raised_exception_messages.append( 117 ''.join(traceback.format_exception(*sys.exc_info()))) 118 119 for agent in self._active_agents_instances + [self]: 120 try: 121 agent.CollectAgentTraceData(builder) 122 except Exception: # pylint: disable=broad-except 123 raised_exception_messages.append( 124 ''.join(traceback.format_exception(*sys.exc_info()))) 125 126 self._iteration_info = None 127 self._active_agents_instances = [] 128 self._current_state = None 129 130 if raised_exception_messages: 131 raise TracingControllerStoppedError( 132 'Exceptions raised when trying to stop tracing:\n' + 133 '\n'.join(raised_exception_messages)) 134 135 return builder.AsData() 136 137 def FlushTracing(self): 138 assert self.is_tracing_running, 'Can only flush tracing when tracing is on.' 139 self._IssueClockSyncMarker() 140 141 raised_exception_messages = [] 142 # Flushing the controller's pytrace is not supported. 143 for agent in self._active_agents_instances: 144 try: 145 if agent.SupportsFlushingAgentTracing(): 146 agent.FlushAgentTracing(self._current_state.config, 147 self._current_state.timeout, 148 self._current_state.builder) 149 except Exception: # pylint: disable=broad-except 150 raised_exception_messages.append( 151 ''.join(traceback.format_exception(*sys.exc_info()))) 152 153 if raised_exception_messages: 154 raise TracingControllerStoppedError( 155 'Exceptions raised when trying to flush tracing:\n' + 156 '\n'.join(raised_exception_messages)) 157 158 def StartAgentTracing(self, config, timeout): 159 self._is_tracing_controllable = self._IsTracingControllable() 160 if not self._is_tracing_controllable: 161 return False 162 163 tf = tempfile.NamedTemporaryFile(delete=False) 164 self._trace_log = tf.name 165 tf.close() 166 del config # unused 167 del timeout # unused 168 assert not trace_event.trace_is_enabled(), 'Tracing already running.' 169 trace_event.trace_enable(self._trace_log) 170 assert trace_event.trace_is_enabled(), 'Tracing didn\'t enable properly.' 171 return True 172 173 def StopAgentTracing(self): 174 if not self._is_tracing_controllable: 175 return 176 assert trace_event.trace_is_enabled(), 'Tracing not running' 177 trace_event.trace_disable() 178 assert not trace_event.trace_is_enabled(), 'Tracing didnt disable properly.' 179 180 def SupportsExplicitClockSync(self): 181 return True 182 183 def _RecordIssuerClockSyncMarker(self, sync_id, issue_ts): 184 """ Record clock sync event. 185 186 Args: 187 sync_id: Unqiue id for sync event. 188 issue_ts: timestamp before issuing clocksync to agent. 189 """ 190 if self._is_tracing_controllable: 191 trace_event.clock_sync(sync_id, issue_ts=issue_ts) 192 193 def _IssueClockSyncMarker(self): 194 with self._DisableGarbageCollection(): 195 for agent in self._active_agents_instances: 196 if agent.SupportsExplicitClockSync(): 197 sync_id = self._GenerateClockSyncId() 198 with trace_event.trace( 199 'RecordClockSyncMarker', 200 agent=str(agent.__class__.__name__), 201 sync_id=sync_id): 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 241 @property 242 def iteration_info(self): 243 return self._iteration_info 244 245 @iteration_info.setter 246 def iteration_info(self, ii): 247 self._iteration_info = ii 248 249 def CollectAgentTraceData(self, trace_data_builder): 250 if not self._is_tracing_controllable: 251 return 252 assert not trace_event.trace_is_enabled(), 'Stop tracing before collection.' 253 with open(self._trace_log, 'r') as fp: 254 data = ast.literal_eval(fp.read() + ']') 255 trace_data_builder.SetTraceFor(trace_data_module.TELEMETRY_PART, { 256 "traceEvents": data, 257 "metadata": { 258 # TODO(charliea): For right now, we use "TELEMETRY" as the clock 259 # domain to guarantee that Telemetry is given its own clock 260 # domain. Telemetry isn't really a clock domain, though: it's a 261 # system that USES a clock domain like LINUX_CLOCK_MONOTONIC or 262 # WIN_QPC. However, there's a chance that a Telemetry controller 263 # running on Linux (using LINUX_CLOCK_MONOTONIC) is interacting with 264 # an Android phone (also using LINUX_CLOCK_MONOTONIC, but on a 265 # different machine). The current logic collapses clock domains 266 # based solely on the clock domain string, but we really should to 267 # collapse based on some (device ID, clock domain ID) tuple. Giving 268 # Telemetry its own clock domain is a work-around for this. 269 "clock-domain": "TELEMETRY", 270 "iteration-info": (self._iteration_info.AsDict() 271 if self._iteration_info else {}), 272 } 273 }) 274 try: 275 os.remove(self._trace_log) 276 self._trace_log = None 277 except OSError: 278 logging.exception('Error when deleting %s, will try again at exit.', 279 self._trace_log) 280 def DeleteAtExit(path): 281 os.remove(path) 282 atexit_with_log.Register(DeleteAtExit, self._trace_log) 283 self._trace_log = None 284