1#!/usr/bin/env python 2 3# Copyright (c) 2011 The Chromium Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7"""Android system-wide tracing utility. 8 9This is a tool for capturing a trace that includes data from both userland and 10the kernel. It creates an HTML file for visualizing the trace. 11""" 12 13# Make sure we're using a new enough version of Python. 14# The flags= parameter of re.sub() is new in Python 2.7. And Systrace does not 15# support Python 3 yet. 16 17import sys 18 19version = sys.version_info[:2] 20if version != (2, 7): 21 sys.stderr.write('This script does not support Python %d.%d. ' 22 'Please use Python 2.7.\n' % version) 23 sys.exit(1) 24 25 26import optparse 27import os 28import time 29 30_SYSTRACE_DIR = os.path.abspath( 31 os.path.join(os.path.dirname(__file__), os.path.pardir)) 32_CATAPULT_DIR = os.path.join( 33 os.path.dirname(os.path.abspath(__file__)), os.path.pardir, os.path.pardir) 34_DEVIL_DIR = os.path.join(_CATAPULT_DIR, 'devil') 35if _DEVIL_DIR not in sys.path: 36 sys.path.insert(0, _DEVIL_DIR) 37if _SYSTRACE_DIR not in sys.path: 38 sys.path.insert(0, _SYSTRACE_DIR) 39 40from devil import devil_env 41from devil.android.sdk import adb_wrapper 42from systrace import systrace_runner 43from systrace import util 44from systrace.tracing_agents import atrace_agent 45from systrace.tracing_agents import atrace_from_file_agent 46from systrace.tracing_agents import battor_trace_agent 47from systrace.tracing_agents import ftrace_agent 48 49ALL_MODULES = [atrace_agent, atrace_from_file_agent, 50 battor_trace_agent, ftrace_agent] 51 52 53def parse_options(argv): 54 """Parses and checks the command-line options. 55 56 Returns: 57 A tuple containing the options structure and a list of categories to 58 be traced. 59 """ 60 usage = 'Usage: %prog [options] [category1 [category2 ...]]' 61 desc = 'Example: %prog -b 32768 -t 15 gfx input view sched freq' 62 parser = optparse.OptionParser(usage=usage, description=desc, 63 conflict_handler='resolve') 64 parser = util.get_main_options(parser) 65 66 parser.add_option('-l', '--list-categories', dest='list_categories', 67 default=False, action='store_true', 68 help='list the available categories and exit') 69 70 # Add the other agent parsing options to the parser. For Systrace on the 71 # command line, all agents are added. For Android, only the compatible agents 72 # will be added. 73 for module in ALL_MODULES: 74 option_group = module.add_options(parser) 75 if option_group: 76 parser.add_option_group(option_group) 77 78 options, categories = parser.parse_args(argv[1:]) 79 80 if options.output_file is None: 81 options.output_file = 'trace.json' if options.write_json else 'trace.html' 82 83 if options.link_assets or options.asset_dir != 'trace-viewer': 84 parser.error('--link-assets and --asset-dir are deprecated.') 85 86 if options.trace_time and options.trace_time < 0: 87 parser.error('the trace time must be a non-negative number') 88 89 if (options.trace_buf_size is not None) and (options.trace_buf_size <= 0): 90 parser.error('the trace buffer size must be a positive number') 91 92 return (options, categories) 93 94def find_adb(): 95 """Finds adb on the path. 96 97 This method is provided to avoid the issue of diskutils.spawn's 98 find_executable which first searches the current directory before 99 searching $PATH. That behavior results in issues where systrace.py 100 uses a different adb than the one in the path. 101 """ 102 paths = os.environ['PATH'].split(os.pathsep) 103 executable = 'adb' 104 if sys.platform == 'win32': 105 executable = executable + '.exe' 106 for p in paths: 107 f = os.path.join(p, executable) 108 if os.path.isfile(f): 109 return f 110 return None 111 112def initialize_devil(): 113 """Initialize devil to use adb from $PATH""" 114 adb_path = find_adb() 115 if adb_path is None: 116 print >> sys.stderr, "Unable to find adb, is it in your path?" 117 sys.exit(1) 118 devil_dynamic_config = { 119 'config_type': 'BaseConfig', 120 'dependencies': { 121 'adb': { 122 'file_info': { 123 devil_env.GetPlatform(): { 124 'local_paths': [os.path.abspath(adb_path)] 125 } 126 } 127 } 128 } 129 } 130 devil_env.config.Initialize(configs=[devil_dynamic_config]) 131 132 133def main_impl(arguments): 134 # Parse the command line options. 135 options, categories = parse_options(arguments) 136 137 # Override --atrace-categories and --ftrace-categories flags if command-line 138 # categories are provided. 139 if categories: 140 if options.target == 'android': 141 options.atrace_categories = categories 142 elif options.target == 'linux': 143 options.ftrace_categories = categories 144 else: 145 raise RuntimeError('Categories are only valid for atrace/ftrace. Target ' 146 'platform must be either Android or Linux.') 147 148 # Include atrace categories by default in Systrace. 149 if options.target == 'android' and not options.atrace_categories: 150 options.atrace_categories = atrace_agent.DEFAULT_CATEGORIES 151 152 if options.target == 'android' and not options.from_file: 153 initialize_devil() 154 if not options.device_serial_number: 155 devices = [a.GetDeviceSerial() for a in adb_wrapper.AdbWrapper.Devices()] 156 if len(devices) == 0: 157 raise RuntimeError('No ADB devices connected.') 158 elif len(devices) >= 2: 159 raise RuntimeError('Multiple devices connected, serial number required') 160 options.device_serial_number = devices[0] 161 162 # If list_categories is selected, just print the list of categories. 163 # In this case, use of the tracing controller is not necessary. 164 if options.list_categories: 165 if options.target == 'android': 166 atrace_agent.list_categories(options) 167 elif options.target == 'linux': 168 ftrace_agent.list_categories(options) 169 return 170 171 # Set up the systrace runner and start tracing. 172 controller = systrace_runner.SystraceRunner( 173 os.path.dirname(os.path.abspath(__file__)), options) 174 controller.StartTracing() 175 176 # Wait for the given number of seconds or until the user presses enter. 177 # pylint: disable=superfluous-parens 178 # (need the parens so no syntax error if trying to load with Python 3) 179 if options.from_file is not None: 180 print('Reading results from file.') 181 elif options.trace_time: 182 print('Starting tracing (%d seconds)' % options.trace_time) 183 time.sleep(options.trace_time) 184 else: 185 raw_input('Starting tracing (stop with enter)') 186 187 # Stop tracing and collect the output. 188 print('Tracing completed. Collecting output...') 189 controller.StopTracing() 190 print('Outputting Systrace results...') 191 controller.OutputSystraceResults(write_json=options.write_json) 192 193def main(): 194 main_impl(sys.argv) 195 196if __name__ == '__main__' and __package__ is None: 197 main() 198