#!/usr/bin/env python # Copyright (c) 2011 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. """Android system-wide tracing utility. This is a tool for capturing a trace that includes data from both userland and the kernel. It creates an HTML file for visualizing the trace. """ # Make sure we're using a new enough version of Python. # The flags= parameter of re.sub() is new in Python 2.7. And Systrace does not # support Python 3 yet. # pylint: disable=wrong-import-position import sys version = sys.version_info[:2] if version != (2, 7): sys.stderr.write('This script does not support Python %d.%d. ' 'Please use Python 2.7.\n' % version) sys.exit(1) import optparse import os import time _SYSTRACE_DIR = os.path.abspath( os.path.join(os.path.dirname(__file__), os.path.pardir)) _CATAPULT_DIR = os.path.join( os.path.dirname(os.path.abspath(__file__)), os.path.pardir, os.path.pardir) _DEVIL_DIR = os.path.join(_CATAPULT_DIR, 'devil') if _DEVIL_DIR not in sys.path: sys.path.insert(0, _DEVIL_DIR) if _SYSTRACE_DIR not in sys.path: sys.path.insert(0, _SYSTRACE_DIR) from devil import devil_env from devil.android.sdk import adb_wrapper from systrace import systrace_runner from systrace import util from systrace.tracing_agents import atrace_agent from systrace.tracing_agents import atrace_from_file_agent from systrace.tracing_agents import atrace_process_dump from systrace.tracing_agents import ftrace_agent from systrace.tracing_agents import walt_agent ALL_MODULES = [atrace_agent, atrace_from_file_agent, atrace_process_dump, ftrace_agent, walt_agent] def parse_options(argv): """Parses and checks the command-line options. Returns: A tuple containing the options structure and a list of categories to be traced. """ usage = 'Usage: %prog [options] [category1 [category2 ...]]' desc = 'Example: %prog -b 32768 -t 15 gfx input view sched freq' parser = optparse.OptionParser(usage=usage, description=desc, conflict_handler='resolve') parser = util.get_main_options(parser) parser.add_option('-l', '--list-categories', dest='list_categories', default=False, action='store_true', help='list the available categories and exit') # Add the other agent parsing options to the parser. For Systrace on the # command line, all agents are added. For Android, only the compatible agents # will be added. for module in ALL_MODULES: option_group = module.add_options(parser) if option_group: parser.add_option_group(option_group) options, categories = parser.parse_args(argv[1:]) if options.output_file is None: base = 'trace' if options.from_file is not None: base = os.path.splitext(options.from_file)[0] suffix = '.json' if options.write_json else '.html' options.output_file = base + suffix if options.link_assets or options.asset_dir != 'trace-viewer': parser.error('--link-assets and --asset-dir are deprecated.') if options.trace_time and options.trace_time < 0: parser.error('the trace time must be a non-negative number') if (options.trace_buf_size is not None) and (options.trace_buf_size <= 0): parser.error('the trace buffer size must be a positive number') return (options, categories) def find_adb(): """Finds adb on the path. This method is provided to avoid the issue of diskutils.spawn's find_executable which first searches the current directory before searching $PATH. That behavior results in issues where systrace.py uses a different adb than the one in the path. """ paths = os.environ['PATH'].split(os.pathsep) executable = 'adb' if sys.platform == 'win32': executable = executable + '.exe' for p in paths: f = os.path.join(p, executable) if os.path.isfile(f): return f return None def initialize_devil(): """Initialize devil to use adb from $PATH""" adb_path = find_adb() if adb_path is None: print >> sys.stderr, "Unable to find adb, is it in your path?" sys.exit(1) devil_dynamic_config = { 'config_type': 'BaseConfig', 'dependencies': { 'adb': { 'file_info': { devil_env.GetPlatform(): { 'local_paths': [os.path.abspath(adb_path)] } } } } } devil_env.config.Initialize(configs=[devil_dynamic_config]) def main_impl(arguments): # Parse the command line options. options, categories = parse_options(arguments) # Override --atrace-categories and --ftrace-categories flags if command-line # categories are provided. if categories: if options.target == 'android': options.atrace_categories = categories elif options.target == 'linux': options.ftrace_categories = categories else: raise RuntimeError('Categories are only valid for atrace/ftrace. Target ' 'platform must be either Android or Linux.') # Include atrace categories by default in Systrace. if options.target == 'android' and not options.atrace_categories: options.atrace_categories = atrace_agent.DEFAULT_CATEGORIES if options.target == 'android' and not options.from_file: initialize_devil() devices = [a.GetDeviceSerial() for a in adb_wrapper.AdbWrapper.Devices()] if not options.device_serial_number: if len(devices) == 0: raise RuntimeError('No ADB devices connected.') elif len(devices) >= 2: raise RuntimeError('Multiple devices connected, serial number required') options.device_serial_number = devices[0] elif options.device_serial_number not in devices: raise RuntimeError('Device with the serial number "%s" is not connected.' % options.device_serial_number) # If list_categories is selected, just print the list of categories. # In this case, use of the tracing controller is not necessary. if options.list_categories: if options.target == 'android': atrace_agent.list_categories(options) elif options.target == 'linux': ftrace_agent.list_categories(options) return # Set up the systrace runner and start tracing. controller = systrace_runner.SystraceRunner( os.path.dirname(os.path.abspath(__file__)), options) controller.StartTracing() # Wait for the given number of seconds or until the user presses enter. # pylint: disable=superfluous-parens # (need the parens so no syntax error if trying to load with Python 3) if options.from_file is not None: print('Reading results from file.') elif options.trace_time: print('Starting tracing (%d seconds)' % options.trace_time) time.sleep(options.trace_time) else: raw_input('Starting tracing (stop with enter)') # Stop tracing and collect the output. print('Tracing completed. Collecting output...') controller.StopTracing() print('Outputting Systrace results...') controller.OutputSystraceResults(write_json=options.write_json) def main(): main_impl(sys.argv) if __name__ == '__main__' and __package__ is None: main()