Index: chromecast/tools/trace.py |
diff --git a/chromecast/tools/trace.py b/chromecast/tools/trace.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..bf93611e0bae34344435424b64f8d85bd4165305 |
--- /dev/null |
+++ b/chromecast/tools/trace.py |
@@ -0,0 +1,185 @@ |
+#!/usr/bin/env python |
+# |
+# Copyright 2015 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. |
+# |
+# This script was originally written by Alok Priyadarshi (alokp@) |
+# with some minor local modifications. |
+ |
+import contextlib |
+import json |
+import logging |
+import math |
+import optparse |
+import os |
+import sys |
+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): |
+ def __init__(self, devtools_port): |
+ self._socket = None |
+ self._next_request_id = 0 |
+ self._tracing_client = None |
+ self._tracing_data = [] |
+ |
+ def Connect(self, device_ip, devtools_port, timeout=10): |
+ 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 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): |
+ 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): |
+ 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): |
+ req['id'] = self._next_request_id |
+ self._next_request_id += 1 |
+ data = json.dumps(req) |
+ self._socket.send(data) |
+ |
+ def _ReceiveResponse(self): |
+ while self._socket: |
+ data = self._socket.recv() |
+ res = json.loads(data) |
+ return res |
+ |
+ def _HandleResponse(self, res): |
+ 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 |
+ |
+ |
+@contextlib.contextmanager |
+def Connect(device_ip, devtools_port): |
+ backend = TracingBackend(devtools_port) |
+ try: |
+ backend.Connect(device_ip, devtools_port) |
+ yield backend |
+ finally: |
+ backend.Disconnect() |
+ |
+ |
+def DumpTrace(trace, options): |
+ filepath = os.path.expanduser(options.output) if options.output \ |
+ else os.path.join(os.getcwd(), 'trace.json') |
+ |
+ dirname = os.path.dirname(filepath) |
+ if dirname: |
+ if not os.path.exists(dirname): |
+ os.makedirs(dirname) |
+ else: |
+ filepath = os.path.join(os.getcwd(), filepath) |
+ |
+ with open(filepath, "w") as f: |
+ json.dump(trace, f) |
+ return filepath |
+ |
+ |
+def _CreateOptionParser(): |
+ parser = optparse.OptionParser(description='Record about://tracing profiles ' |
+ 'from any running instance of Chrome.') |
+ parser.add_option( |
+ '-v', '--verbose', help='Verbose logging.', action='store_true') |
+ parser.add_option( |
+ '-p', '--port', help='Remote debugging port.', type="int", default=9222) |
+ parser.add_option( |
+ '-d', '--device', help='Device ip address.', type='string', |
+ default='127.0.0.1') |
+ |
+ tracing_opts = optparse.OptionGroup(parser, 'Tracing options') |
+ tracing_opts.add_option( |
+ '-c', '--category-filter', |
+ help='Apply filter to control what category groups should be traced.', |
+ type='string') |
+ tracing_opts.add_option( |
+ '--record-continuously', |
+ help='Keep recording until stopped. The trace buffer is of fixed size ' |
+ 'and used as a ring buffer. If this option is omitted then ' |
+ 'recording stops when the trace buffer is full.', |
+ action='store_true') |
+ parser.add_option_group(tracing_opts) |
+ |
+ output_options = optparse.OptionGroup(parser, 'Output options') |
+ output_options.add_option( |
+ '-o', '--output', |
+ help='Save trace output to file.') |
+ parser.add_option_group(output_options) |
+ |
+ return parser |
+ |
+ |
+def _ProcessOptions(options): |
+ websocket.enableTrace(options.verbose) |
+ |
+ |
+def main(): |
+ parser = _CreateOptionParser() |
+ options, _args = parser.parse_args() |
+ _ProcessOptions(options) |
+ |
+ with Connect(options.device, options.port) as tracing_backend: |
+ tracing_backend.StartTracing(TracingClient(), |
+ options.category_filter, |
+ options.record_continuously) |
+ raw_input('Capturing trace. Press Enter to stop...') |
+ trace = tracing_backend.StopTracing() |
+ |
+ filepath = DumpTrace(trace, options) |
+ print('Done') |
+ print('Trace written to file://%s' % filepath) |
+ |
+ |
+if __name__ == '__main__': |
+ sys.exit(main()) |