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