| Index: build/android/adb_profile_chrome_startup.py
|
| diff --git a/build/android/adb_profile_chrome_startup.py b/build/android/adb_profile_chrome_startup.py
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..83428edafea46a935b66b2945ea90cd09845002c
|
| --- /dev/null
|
| +++ b/build/android/adb_profile_chrome_startup.py
|
| @@ -0,0 +1,294 @@
|
| +#!/usr/bin/env python
|
| +#
|
| +# Copyright 2013 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.
|
| +
|
| +import gzip
|
| +import logging
|
| +import optparse
|
| +import os
|
| +import re
|
| +import select
|
| +import shutil
|
| +import sys
|
| +import threading
|
| +import time
|
| +import webbrowser
|
| +import zipfile
|
| +import zlib
|
| +
|
| +from pylib import android_commands
|
| +from pylib import cmd_helper
|
| +from pylib import constants
|
| +
|
| +_TRACE_VIEWER_ROOT = os.path.join(constants.DIR_SOURCE_ROOT,
|
| + 'third_party', 'trace-viewer')
|
| +sys.path.append(_TRACE_VIEWER_ROOT)
|
| +from trace_viewer.build import trace2html # pylint: disable=F0401
|
| +
|
| +
|
| +def _GetTraceTimestamp():
|
| + return time.strftime('%Y-%m-%d-%H%M%S', time.localtime())
|
| +
|
| +
|
| +class ChromeTracingController(object):
|
| + def __init__(self, adb):
|
| + self._adb = adb
|
| + self._trace_start_re = \
|
| + re.compile(r'Logging performance trace to file: (.*)')
|
| + self._trace_finish_re = \
|
| + re.compile(r'Profiler finished[.] Results are in (.*)[.]')
|
| +
|
| + def __str__(self):
|
| + return 'chrome trace'
|
| +
|
| + def StartTracing(self):
|
| + pass
|
| +
|
| + def StopTracing(self):
|
| + pass
|
| +
|
| + def PullTrace(self):
|
| + # Wait a bit for the browser to finish writing the trace file.
|
| + time.sleep(3)
|
| +
|
| + trace_file = '/data/data/com.google.android.apps.chrome/cache/chrome-trace'
|
| + host_file = os.path.join(os.path.curdir, os.path.basename(trace_file))
|
| + self._adb.PullFileFromDevice(trace_file, host_file)
|
| + return host_file
|
| +
|
| +
|
| +_SYSTRACE_OPTIONS = [
|
| + # Compress the trace before sending it over USB.
|
| + '-z',
|
| + # Use a large trace buffer to increase the polling interval.
|
| + '-b', '16384'
|
| +]
|
| +
|
| +# Interval in seconds for sampling systrace data.
|
| +_SYSTRACE_INTERVAL = 15
|
| +
|
| +
|
| +class SystraceController(object):
|
| + def __init__(self, adb, categories):
|
| + self._adb = adb
|
| + self._categories = categories
|
| + self._done = threading.Event()
|
| + self._thread = None
|
| + self._trace_data = None
|
| +
|
| + def __str__(self):
|
| + return 'systrace'
|
| +
|
| + @staticmethod
|
| + def GetCategories(adb):
|
| + return adb.RunShellCommand('atrace --list_categories')
|
| +
|
| + def StartTracing(self):
|
| + self._thread = threading.Thread(target=self._CollectData)
|
| + self._thread.start()
|
| +
|
| + def StopTracing(self):
|
| + self._done.set()
|
| +
|
| + def PullTrace(self):
|
| + self._thread.join()
|
| + self._thread = None
|
| + if self._trace_data:
|
| + output_name = 'systrace-%s' % _GetTraceTimestamp()
|
| + with open(output_name, 'w') as out:
|
| + out.write(self._trace_data)
|
| + return output_name
|
| +
|
| + def _RunATraceCommand(self, command):
|
| + # We use a separate interface to adb because the one from AndroidCommands
|
| + # isn't re-entrant.
|
| + device = ['-s', self._adb.GetDevice()] if self._adb.GetDevice() else []
|
| + cmd = ['adb'] + device + ['shell', 'atrace', '--%s' % command] + \
|
| + _SYSTRACE_OPTIONS + self._categories
|
| + return cmd_helper.GetCmdOutput(cmd)
|
| +
|
| + def _CollectData(self):
|
| + trace_data = []
|
| + self._RunATraceCommand('async_start')
|
| + try:
|
| + while not self._done.is_set():
|
| + self._done.wait(_SYSTRACE_INTERVAL)
|
| + if self._done.is_set():
|
| + trace_data.append(
|
| + self._DecodeTraceData(self._RunATraceCommand('async_dump')))
|
| + finally:
|
| + trace_data.append(
|
| + self._DecodeTraceData(self._RunATraceCommand('async_stop')))
|
| + self._trace_data = ''.join([zlib.decompress(d) for d in trace_data])
|
| +
|
| + @staticmethod
|
| + def _DecodeTraceData(trace_data):
|
| + try:
|
| + trace_start = trace_data.index('TRACE:')
|
| + except ValueError:
|
| + raise RuntimeError('Systrace start marker not found')
|
| + trace_data = trace_data[trace_start + 6:]
|
| +
|
| + # Collapse CRLFs that are added by adb shell.
|
| + if trace_data.startswith('\r\n'):
|
| + trace_data = trace_data.replace('\r\n', '\n')
|
| +
|
| + # Skip the initial newline.
|
| + return trace_data[1:]
|
| +
|
| +def _CompressFile(host_file, output):
|
| + with gzip.open(output, 'wb') as out:
|
| + with open(host_file, 'rb') as input_file:
|
| + out.write(input_file.read())
|
| + os.unlink(host_file)
|
| +
|
| +
|
| +def _ArchiveFiles(host_files, output):
|
| + print "_ArchiveFiles"
|
| + with zipfile.ZipFile(output, 'w', zipfile.ZIP_DEFLATED) as z:
|
| + for host_file in host_files:
|
| + z.write(host_file)
|
| + os.unlink(host_file)
|
| +
|
| +
|
| +def _PackageTracesAsHtml(trace_files, html_file):
|
| + with open(html_file, 'w') as f:
|
| + trace2html.WriteHTMLForTracesToFile(trace_files, f)
|
| + for trace_file in trace_files:
|
| + os.unlink(trace_file)
|
| +
|
| +
|
| +def _PrintMessage(heading, eol='\n'):
|
| + sys.stdout.write('%s%s' % (heading, eol))
|
| + sys.stdout.flush()
|
| +
|
| +
|
| +def _WaitForEnter(timeout):
|
| + select.select([sys.stdin], [], [], timeout)
|
| +
|
| +
|
| +def _StartTracing(controllers):
|
| + for controller in controllers:
|
| + controller.StartTracing()
|
| + time.sleep(1)
|
| +
|
| +
|
| +def _StopTracing(controllers):
|
| + for controller in controllers:
|
| + controller.StopTracing()
|
| +
|
| +
|
| +def _PullTraces(controllers, output, write_json):
|
| + _PrintMessage('Downloading...', eol='')
|
| + trace_files = []
|
| + for controller in controllers:
|
| + trace_files.append(controller.PullTrace())
|
| +
|
| + if not write_json:
|
| + html_file = os.path.splitext(trace_files[0])[0] + '.html'
|
| + _PackageTracesAsHtml(trace_files, html_file)
|
| + trace_files = [html_file]
|
| +
|
| + if len(trace_files) > 1:
|
| + result = output or 'chrome-combined-trace-%s.zip' % _GetTraceTimestamp()
|
| + _ArchiveFiles(trace_files, result)
|
| + elif output:
|
| + result = output
|
| + shutil.move(trace_files[0], result)
|
| + else:
|
| + result = trace_files[0]
|
| +
|
| + _PrintMessage('done')
|
| + _PrintMessage('Trace written to file://%s' % os.path.abspath(result))
|
| + return result
|
| +
|
| +
|
| +def _CaptureAndPullTrace(adb, controllers, output, write_json):
|
| + trace_type = ' + '.join(map(str, controllers))
|
| + try:
|
| + adb.RunShellCommand(
|
| + 'echo "chrome --trace-startup '
|
| + '--trace-startup-file='
|
| + '/data/data/com.google.android.apps.chrome/cache/chrome-trace"'
|
| + '> /data/local/chrome-command-line')
|
| +
|
| + adb.RunShellCommand('echo 1 > /proc/sys/vm/drop_caches')
|
| + _StartTracing(controllers)
|
| + adb.RunShellCommand(
|
| + 'am start -a android.intent.action.VIEW -n '
|
| + 'com.google.android.apps.chrome/.Main -d google.com')
|
| + _PrintMessage('Capturing %s. Press Enter to stop...' % trace_type, eol='')
|
| + raw_input()
|
| + finally:
|
| + _StopTracing(controllers)
|
| + _PrintMessage('done')
|
| +
|
| + return _PullTraces(controllers, output, write_json)
|
| +
|
| +
|
| +def _ComputeSystraceCategories(options):
|
| + if not options.systrace_categories:
|
| + return []
|
| + return options.systrace_categories.split(',')
|
| +
|
| +
|
| +def main():
|
| + parser = optparse.OptionParser(description='Record about://tracing profiles '
|
| + 'from Android browsers. See http://dev.'
|
| + 'chromium.org/developers/how-tos/trace-event-'
|
| + 'profiling-tool for detailed instructions for '
|
| + 'profiling.')
|
| +
|
| +
|
| + categories = optparse.OptionGroup(parser, 'Trace categories')
|
| + categories.add_option('-s', '--systrace', help='Capture a systrace with the '
|
| + 'chosen comma-delimited systrace categories. You can '
|
| + 'also capture a combined Chrome + systrace by enabling '
|
| + 'both types of categories. Use "list" to see the '
|
| + 'available categories. Systrace is disabled by '
|
| + 'default.', metavar='SYS_CATEGORIES',
|
| + dest='systrace_categories', default='')
|
| + categories.add_option('--trace-flow', help='Enable extra trace categories '
|
| + 'for IPC message flows.', action='store_true')
|
| + parser.add_option_group(categories)
|
| +
|
| + output_options = optparse.OptionGroup(parser, 'Output options')
|
| + output_options.add_option('-o', '--output', help='Save trace output to file.')
|
| + output_options.add_option('--json', help='Save trace as raw JSON instead of '
|
| + 'HTML.', action='store_true')
|
| + output_options.add_option('--view', help='Open resulting trace file in a '
|
| + 'browser.', action='store_true')
|
| + parser.add_option_group(output_options)
|
| +
|
| + parser.add_option('-v', '--verbose', help='Verbose logging.',
|
| + action='store_true')
|
| + options, _args = parser.parse_args()
|
| +
|
| + if options.verbose:
|
| + logging.getLogger().setLevel(logging.DEBUG)
|
| +
|
| + adb = android_commands.AndroidCommands()
|
| + if options.systrace_categories in ['list', 'help']:
|
| + _PrintMessage('\n'.join(SystraceController.GetCategories(adb)))
|
| + return 0
|
| +
|
| + systrace_categories = _ComputeSystraceCategories(options)
|
| +
|
| + controllers = []
|
| + controllers.append(SystraceController(adb, systrace_categories))
|
| + controllers.append(ChromeTracingController(adb))
|
| +
|
| + if options.output:
|
| + options.output = os.path.expanduser(options.output)
|
| + result = _CaptureAndPullTrace(adb, controllers, options.output, options.json)
|
| + if options.view:
|
| + if sys.platform == 'darwin':
|
| + os.system('/usr/bin/open %s' % os.path.abspath(result))
|
| + else:
|
| + webbrowser.open(result)
|
| +
|
| +
|
| +if __name__ == '__main__':
|
| + sys.exit(main())
|
|
|