# Copyright 2016 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Utilities for capturing traces for chromecast devices.""" import json import logging import math import websocket class TracingClient(object): def BufferUsage(self, buffer_usage): percent = int(math.floor(buffer_usage * 100)) logging.debug('Buffer Usage: %i', percent) class TracingBackend(object): """Class for starting a tracing session with cast_shell.""" def __init__(self): self._socket = None self._next_request_id = 0 self._tracing_client = None self._tracing_data = [] def Connect(self, device_ip, devtools_port=9222, timeout=10): """Connect to cast_shell on given device and port. Args: device_ip: IP of device to connect to. devtools_port: Remote dev tool port to connect to. Defaults to 9222. timeout: Amount of time to wait for connection in seconds. Default 10s. """ assert not self._socket url = 'ws://%s:%i/devtools/browser' % (device_ip, devtools_port) print('Connect to %s ...' % url) self._socket = websocket.create_connection(url, timeout=timeout) self._next_request_id = 0 def Disconnect(self): """If connected to device, disconnect from device.""" if self._socket: self._socket.close() self._socket = None def StartTracing(self, tracing_client=None, custom_categories=None, record_continuously=False, buffer_usage_reporting_interval=0, timeout=10): """Begin a tracing session on device. Args: tracing_client: client for this tracing session. custom_categories: Categories to filter for. None records all categories. record_continuously: Keep tracing until stopped. If false, will exit when buffer is full. buffer_usage_reporting_interval: How often to report buffer usage. timeout: Time to wait to start tracing in seconds. Default 10s. """ self._tracing_client = tracing_client self._socket.settimeout(timeout) req = { 'method': 'Tracing.start', 'params': { 'categories': custom_categories, 'bufferUsageReportingInterval': buffer_usage_reporting_interval, 'options': 'record-continuously' if record_continuously else 'record-until-full' } } self._SendRequest(req) def StopTracing(self, timeout=30): """End a tracing session on device. Args: timeout: Time to wait to stop tracing in seconds. Default 30s. Returns: Trace file for the stopped session. """ self._socket.settimeout(timeout) req = {'method': 'Tracing.end'} self._SendRequest(req) while self._socket: res = self._ReceiveResponse() if 'method' in res and self._HandleResponse(res): self._tracing_client = None result = self._tracing_data self._tracing_data = [] return result def _SendRequest(self, req): """Sends request to remote devtools. Args: req: Request to send. """ req['id'] = self._next_request_id self._next_request_id += 1 data = json.dumps(req) self._socket.send(data) def _ReceiveResponse(self): """Get response from remote devtools. Returns: Response received. """ while self._socket: data = self._socket.recv() res = json.loads(data) return res def _HandleResponse(self, res): """Handle response from remote devtools. Args: res: Recieved tresponse that should be handled. """ method = res.get('method') value = res.get('params', {}).get('value') if 'Tracing.dataCollected' == method: if type(value) in [str, unicode]: self._tracing_data.append(value) elif type(value) is list: self._tracing_data.extend(value) else: logging.warning('Unexpected type in tracing data') elif 'Tracing.bufferUsage' == method and self._tracing_client: self._tracing_client.BufferUsage(value) elif 'Tracing.tracingComplete' == method: return True