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 exceptions 19from telemetry.core import util 20from telemetry.internal.platform import tracing_agent 21from telemetry.internal.platform.tracing_agent import chrome_tracing_agent 22from telemetry.timeline import tracing_config 23from tracing.trace_data import trace_data as trace_data_module 24 25 26def _IterAllTracingAgentClasses(): 27 tracing_agent_dir = os.path.join( 28 os.path.dirname(os.path.realpath(__file__)), 'tracing_agent') 29 return discover.DiscoverClasses( 30 tracing_agent_dir, util.GetTelemetryDir(), 31 tracing_agent.TracingAgent).itervalues() 32 33 34class _TracingState(object): 35 36 def __init__(self, config, timeout): 37 self._builder = trace_data_module.TraceDataBuilder() 38 self._config = config 39 self._timeout = timeout 40 41 @property 42 def builder(self): 43 return self._builder 44 45 @property 46 def config(self): 47 return self._config 48 49 @property 50 def timeout(self): 51 return self._timeout 52 53 54class TracingControllerBackend(object): 55 def __init__(self, platform_backend): 56 self._platform_backend = platform_backend 57 self._current_state = None 58 self._supported_agents_classes = [ 59 agent_classes for agent_classes in _IterAllTracingAgentClasses() if 60 agent_classes.IsSupported(platform_backend)] 61 self._active_agents_instances = [] 62 self._trace_log = None 63 self._is_tracing_controllable = True 64 self._telemetry_info = None 65 66 def StartTracing(self, config, timeout): 67 if self.is_tracing_running: 68 return False 69 70 assert isinstance(config, tracing_config.TracingConfig) 71 assert len(self._active_agents_instances) == 0 72 73 self._current_state = _TracingState(config, timeout) 74 # Hack: chrome tracing agent may only depend on the number of alive chrome 75 # devtools processes, rather platform (when startup tracing is not 76 # supported), hence we add it to the list of supported agents here if it was 77 # not added. 78 if (chrome_tracing_agent.ChromeTracingAgent.IsSupported( 79 self._platform_backend) and 80 not chrome_tracing_agent.ChromeTracingAgent in 81 self._supported_agents_classes): 82 self._supported_agents_classes.append( 83 chrome_tracing_agent.ChromeTracingAgent) 84 85 self.StartAgentTracing(config, timeout) 86 for agent_class in self._supported_agents_classes: 87 agent = agent_class(self._platform_backend) 88 with trace_event.trace( 89 'StartAgentTracing', 90 agent=str(agent.__class__.__name__)): 91 started = agent.StartAgentTracing(config, timeout) 92 if started: 93 self._active_agents_instances.append(agent) 94 return True 95 96 def _GenerateClockSyncId(self): 97 return str(uuid.uuid4()) 98 99 @contextlib.contextmanager 100 def _DisableGarbageCollection(self): 101 try: 102 gc.disable() 103 yield 104 finally: 105 gc.enable() 106 107 def StopTracing(self): 108 assert self.is_tracing_running, 'Can only stop tracing when tracing is on.' 109 self._IssueClockSyncMarker() 110 builder = self._current_state.builder 111 112 raised_exception_messages = [] 113 for agent in self._active_agents_instances + [self]: 114 try: 115 with trace_event.trace( 116 'StopAgentTracing', 117 agent=str(agent.__class__.__name__)): 118 agent.StopAgentTracing() 119 except Exception: # pylint: disable=broad-except 120 raised_exception_messages.append( 121 ''.join(traceback.format_exception(*sys.exc_info()))) 122 123 for agent in self._active_agents_instances + [self]: 124 try: 125 with trace_event.trace( 126 'CollectAgentTraceData', 127 agent=str(agent.__class__.__name__)): 128 agent.CollectAgentTraceData(builder) 129 except Exception: # pylint: disable=broad-except 130 raised_exception_messages.append( 131 ''.join(traceback.format_exception(*sys.exc_info()))) 132 133 self._telemetry_info = None 134 self._active_agents_instances = [] 135 self._current_state = None 136 137 if raised_exception_messages: 138 raise exceptions.Error( 139 'Exceptions raised when trying to stop tracing:\n' + 140 '\n'.join(raised_exception_messages)) 141 142 return builder.AsData() 143 144 def FlushTracing(self): 145 assert self.is_tracing_running, 'Can only flush tracing when tracing is on.' 146 self._IssueClockSyncMarker() 147 148 raised_exception_messages = [] 149 # Flushing the controller's pytrace is not supported. 150 for agent in self._active_agents_instances: 151 try: 152 if agent.SupportsFlushingAgentTracing(): 153 with trace_event.trace( 154 'FlushAgentTracing', 155 agent=str(agent.__class__.__name__)): 156 agent.FlushAgentTracing(self._current_state.config, 157 self._current_state.timeout, 158 self._current_state.builder) 159 except Exception: # pylint: disable=broad-except 160 raised_exception_messages.append( 161 ''.join(traceback.format_exception(*sys.exc_info()))) 162 163 if raised_exception_messages: 164 raise exceptions.Error( 165 'Exceptions raised when trying to flush tracing:\n' + 166 '\n'.join(raised_exception_messages)) 167 168 def StartAgentTracing(self, config, timeout): 169 self._is_tracing_controllable = self._IsTracingControllable() 170 if not self._is_tracing_controllable: 171 return False 172 173 tf = tempfile.NamedTemporaryFile(delete=False) 174 self._trace_log = tf.name 175 tf.close() 176 del config # unused 177 del timeout # unused 178 assert not trace_event.trace_is_enabled(), 'Tracing already running.' 179 trace_event.trace_enable(self._trace_log) 180 assert trace_event.trace_is_enabled(), 'Tracing didn\'t enable properly.' 181 return True 182 183 def StopAgentTracing(self): 184 if not self._is_tracing_controllable: 185 return 186 assert trace_event.trace_is_enabled(), 'Tracing not running' 187 trace_event.trace_disable() 188 assert not trace_event.trace_is_enabled(), 'Tracing didnt disable properly.' 189 190 def SupportsExplicitClockSync(self): 191 return True 192 193 def _RecordIssuerClockSyncMarker(self, sync_id, issue_ts): 194 """ Record clock sync event. 195 196 Args: 197 sync_id: Unqiue id for sync event. 198 issue_ts: timestamp before issuing clocksync to agent. 199 """ 200 if self._is_tracing_controllable: 201 trace_event.clock_sync(sync_id, issue_ts=issue_ts) 202 203 def _IssueClockSyncMarker(self): 204 with self._DisableGarbageCollection(): 205 for agent in self._active_agents_instances: 206 if agent.SupportsExplicitClockSync(): 207 sync_id = self._GenerateClockSyncId() 208 with trace_event.trace( 209 'RecordClockSyncMarker', 210 agent=str(agent.__class__.__name__), 211 sync_id=sync_id): 212 agent.RecordClockSyncMarker(sync_id, 213 self._RecordIssuerClockSyncMarker) 214 215 def IsChromeTracingSupported(self): 216 return chrome_tracing_agent.ChromeTracingAgent.IsSupported( 217 self._platform_backend) 218 219 @property 220 def is_tracing_running(self): 221 return self._current_state is not None 222 223 def _GetActiveChromeTracingAgent(self): 224 if not self.is_tracing_running: 225 return None 226 if not self._current_state.config.enable_chrome_trace: 227 return None 228 for agent in self._active_agents_instances: 229 if isinstance(agent, chrome_tracing_agent.ChromeTracingAgent): 230 return agent 231 return None 232 233 def GetChromeTraceConfig(self): 234 agent = self._GetActiveChromeTracingAgent() 235 if agent: 236 return agent.trace_config 237 return None 238 239 def GetChromeTraceConfigFile(self): 240 agent = self._GetActiveChromeTracingAgent() 241 if agent: 242 return agent.trace_config_file 243 return None 244 245 def _IsTracingControllable(self): 246 return trace_event.is_tracing_controllable() 247 248 def ClearStateIfNeeded(self): 249 chrome_tracing_agent.ClearStarupTracingStateIfNeeded(self._platform_backend) 250 251 @property 252 def telemetry_info(self): 253 return self._telemetry_info 254 255 @telemetry_info.setter 256 def telemetry_info(self, ii): 257 self._telemetry_info = ii 258 259 def CollectAgentTraceData(self, trace_data_builder): 260 if not self._is_tracing_controllable: 261 return 262 assert not trace_event.trace_is_enabled(), 'Stop tracing before collection.' 263 with open(self._trace_log, 'r') as fp: 264 data = ast.literal_eval(fp.read() + ']') 265 trace_data_builder.AddTraceFor(trace_data_module.TELEMETRY_PART, { 266 "traceEvents": data, 267 "metadata": { 268 # TODO(charliea): For right now, we use "TELEMETRY" as the clock 269 # domain to guarantee that Telemetry is given its own clock 270 # domain. Telemetry isn't really a clock domain, though: it's a 271 # system that USES a clock domain like LINUX_CLOCK_MONOTONIC or 272 # WIN_QPC. However, there's a chance that a Telemetry controller 273 # running on Linux (using LINUX_CLOCK_MONOTONIC) is interacting with 274 # an Android phone (also using LINUX_CLOCK_MONOTONIC, but on a 275 # different machine). The current logic collapses clock domains 276 # based solely on the clock domain string, but we really should to 277 # collapse based on some (device ID, clock domain ID) tuple. Giving 278 # Telemetry its own clock domain is a work-around for this. 279 "clock-domain": "TELEMETRY", 280 "telemetry": (self._telemetry_info.AsDict() 281 if self._telemetry_info else {}), 282 } 283 }) 284 try: 285 os.remove(self._trace_log) 286 self._trace_log = None 287 except OSError: 288 logging.exception('Error when deleting %s, will try again at exit.', 289 self._trace_log) 290 def DeleteAtExit(path): 291 os.remove(path) 292 atexit_with_log.Register(DeleteAtExit, self._trace_log) 293 self._trace_log = None 294