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 48from systrace.tracing_agents import walt_agent 49 50 51ALL_MODULES = [atrace_agent, atrace_from_file_agent, 52 battor_trace_agent, ftrace_agent, walt_agent] 53 54 55def parse_options(argv): 56 """Parses and checks the command-line options. 57 58 Returns: 59 A tuple containing the options structure and a list of categories to 60 be traced. 61 """ 62 usage = 'Usage: %prog [options] [category1 [category2 ...]]' 63 desc = 'Example: %prog -b 32768 -t 15 gfx input view sched freq' 64 parser = optparse.OptionParser(usage=usage, description=desc, 65 conflict_handler='resolve') 66 parser = util.get_main_options(parser) 67 68 parser.add_option('-l', '--list-categories', dest='list_categories', 69 default=False, action='store_true', 70 help='list the available categories and exit') 71 72 # Add the other agent parsing options to the parser. For Systrace on the 73 # command line, all agents are added. For Android, only the compatible agents 74 # will be added. 75 for module in ALL_MODULES: 76 option_group = module.add_options(parser) 77 if option_group: 78 parser.add_option_group(option_group) 79 80 options, categories = parser.parse_args(argv[1:]) 81 82 if options.output_file is None: 83 options.output_file = 'trace.json' if options.write_json else 'trace.html' 84 85 if options.link_assets or options.asset_dir != 'trace-viewer': 86 parser.error('--link-assets and --asset-dir are deprecated.') 87 88 if options.trace_time and options.trace_time < 0: 89 parser.error('the trace time must be a non-negative number') 90 91 if (options.trace_buf_size is not None) and (options.trace_buf_size <= 0): 92 parser.error('the trace buffer size must be a positive number') 93 94 return (options, categories) 95 96def find_adb(): 97 """Finds adb on the path. 98 99 This method is provided to avoid the issue of diskutils.spawn's 100 find_executable which first searches the current directory before 101 searching $PATH. That behavior results in issues where systrace.py 102 uses a different adb than the one in the path. 103 """ 104 paths = os.environ['PATH'].split(os.pathsep) 105 executable = 'adb' 106 if sys.platform == 'win32': 107 executable = executable + '.exe' 108 for p in paths: 109 f = os.path.join(p, executable) 110 if os.path.isfile(f): 111 return f 112 return None 113 114def initialize_devil(): 115 """Initialize devil to use adb from $PATH""" 116 adb_path = find_adb() 117 if adb_path is None: 118 print >> sys.stderr, "Unable to find adb, is it in your path?" 119 sys.exit(1) 120 devil_dynamic_config = { 121 'config_type': 'BaseConfig', 122 'dependencies': { 123 'adb': { 124 'file_info': { 125 devil_env.GetPlatform(): { 126 'local_paths': [os.path.abspath(adb_path)] 127 } 128 } 129 } 130 } 131 } 132 devil_env.config.Initialize(configs=[devil_dynamic_config]) 133 134 135def main_impl(arguments): 136 # Parse the command line options. 137 options, categories = parse_options(arguments) 138 139 # Override --atrace-categories and --ftrace-categories flags if command-line 140 # categories are provided. 141 if categories: 142 if options.target == 'android': 143 options.atrace_categories = categories 144 elif options.target == 'linux': 145 options.ftrace_categories = categories 146 else: 147 raise RuntimeError('Categories are only valid for atrace/ftrace. Target ' 148 'platform must be either Android or Linux.') 149 150 # Include atrace categories by default in Systrace. 151 if options.target == 'android' and not options.atrace_categories: 152 options.atrace_categories = atrace_agent.DEFAULT_CATEGORIES 153 154 if options.target == 'android' and not options.from_file: 155 initialize_devil() 156 if not options.device_serial_number: 157 devices = [a.GetDeviceSerial() for a in adb_wrapper.AdbWrapper.Devices()] 158 if len(devices) == 0: 159 raise RuntimeError('No ADB devices connected.') 160 elif len(devices) >= 2: 161 raise RuntimeError('Multiple devices connected, serial number required') 162 options.device_serial_number = devices[0] 163 164 # If list_categories is selected, just print the list of categories. 165 # In this case, use of the tracing controller is not necessary. 166 if options.list_categories: 167 if options.target == 'android': 168 atrace_agent.list_categories(options) 169 elif options.target == 'linux': 170 ftrace_agent.list_categories(options) 171 return 172 173 # Set up the systrace runner and start tracing. 174 controller = systrace_runner.SystraceRunner( 175 os.path.dirname(os.path.abspath(__file__)), options) 176 controller.StartTracing() 177 178 # Wait for the given number of seconds or until the user presses enter. 179 # pylint: disable=superfluous-parens 180 # (need the parens so no syntax error if trying to load with Python 3) 181 if options.from_file is not None: 182 print('Reading results from file.') 183 elif options.trace_time: 184 print('Starting tracing (%d seconds)' % options.trace_time) 185 time.sleep(options.trace_time) 186 else: 187 raw_input('Starting tracing (stop with enter)') 188 189 # Stop tracing and collect the output. 190 print('Tracing completed. Collecting output...') 191 controller.StopTracing() 192 print('Outputting Systrace results...') 193 controller.OutputSystraceResults(write_json=options.write_json) 194 195def main(): 196 main_impl(sys.argv) 197 198if __name__ == '__main__' and __package__ is None: 199 main() 200