1#!/usr/bin/env python2.6 2# 3# Copyright (C) 2011 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17 18# 19# Remotely controls an OProfile session on an Android device. 20# 21 22import os 23import sys 24import subprocess 25import getopt 26import re 27import shutil 28 29 30# Find oprofile binaries (compiled on the host) 31try: 32 oprofile_bin_dir = os.environ['OPROFILE_BIN_DIR'] 33except: 34 try: 35 android_host_out = os.environ['ANDROID_HOST_OUT'] 36 except: 37 print "Either OPROFILE_BIN_DIR or ANDROID_HOST_OUT must be set. Run \". envsetup.sh\" first" 38 sys.exit(1) 39 oprofile_bin_dir = os.path.join(android_host_out, 'bin') 40 41opimport_bin = os.path.join(oprofile_bin_dir, 'opimport') 42opreport_bin = os.path.join(oprofile_bin_dir, 'opreport') 43opannotate_bin = os.path.join(oprofile_bin_dir, 'opannotate') 44 45 46# Find symbol directories 47try: 48 android_product_out = os.environ['ANDROID_PRODUCT_OUT'] 49except: 50 print "ANDROID_PRODUCT_OUT must be set. Run \". envsetup.sh\" first" 51 sys.exit(1) 52 53symbols_dir = os.path.join(android_product_out, 'symbols') 54system_dir = os.path.join(android_product_out, 'system') 55 56 57def execute(command, echo=True): 58 if echo: 59 print ' '.join(command) 60 popen = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 61 output = '' 62 while True: 63 stdout, stderr = popen.communicate() 64 if echo and len(stdout) != 0: 65 print stdout 66 if echo and len(stderr) != 0: 67 print stderr 68 output += stdout 69 output += stderr 70 rc = popen.poll() 71 if rc is not None: 72 break 73 if echo: 74 print 'exit code: %d' % rc 75 return rc, output 76 77# ADB wrapper 78class Adb: 79 def __init__(self, serial_number): 80 self._base_args = ['adb'] 81 if serial_number != None: 82 self._base_args.append('-s') 83 self._base_args.append(serial_number) 84 85 def shell(self, command_args, echo=True): 86 return self._adb('shell', command_args, echo) 87 88 def pull(self, source, dest, echo=True): 89 return self._adb('pull', [source, dest], echo) 90 91 def _adb(self, command, command_args, echo): 92 return execute(self._base_args + [command] + command_args, echo) 93 94 95# The tool program itself 96class Tool: 97 def __init__(self, argv): 98 self.argv = argv 99 self.verbose = False 100 self.session_dir = '/tmp/oprofile' 101 102 def usage(self): 103 print "Usage: " + self.argv[0] + " [options] <command> [command args]" 104 print 105 print " Options:" 106 print 107 print " -h, --help : show this help text" 108 print " -s, --serial=number : the serial number of the device being profiled" 109 print " -v, --verbose : show verbose output" 110 print " -d, --dir=path : directory to store oprofile session on the host, default: /tmp/oprofile" 111 print 112 print " Commands:" 113 print 114 print " setup [args] : setup profiler with specified arguments to 'opcontrol --setup'" 115 print " -t, --timer : enable timer based profiling" 116 print " -e, --event=[spec] : specify an event type to profile, eg. --event=CPU_CYCLES:100000" 117 print " (not supported on all devices)" 118 print " -c, --callgraph=[depth] : specify callgraph capture depth, default is none" 119 print " (not supported in timer mode)" 120 print " -k, --kernel-image : specifies the location of a kernel image relative to the symbols directory" 121 print " (and turns on kernel profiling). This need not be the same as the" 122 print " location of the kernel on the actual device." 123 print 124 print " shutdown : shutdown profiler" 125 print 126 print " start : start profiling" 127 print 128 print " stop : stop profiling" 129 print 130 print " status : show profiler status" 131 print 132 print " import : dump samples and pull session directory from the device" 133 print " -f, --force : remove existing session directory before import" 134 print 135 print " report [args] : generate report with specified arguments to 'opreport'" 136 print " -l, --symbols : show symbols" 137 print " -c, --callgraph : show callgraph" 138 print " --help : show help for additional opreport options" 139 print 140 print " annotate [args] : generate annotation with specified arguments to 'annotation'" 141 print " -s, --source : show source" 142 print " -a, --assembly : show assembly" 143 print " --help : show help for additional opannotate options" 144 print 145 146 def main(self): 147 rc = self.do_main() 148 if rc == 2: 149 print 150 self.usage() 151 return rc 152 153 def do_main(self): 154 try: 155 opts, args = getopt.getopt(self.argv[1:], 156 'hs:vd', ['help', 'serial=', 'dir=', 'verbose']) 157 except getopt.GetoptError, e: 158 print str(e) 159 return 2 160 161 serial_number = None 162 for o, a in opts: 163 if o in ('-h', '--help'): 164 self.usage() 165 return 0 166 elif o in ('-s', '--serial'): 167 serial_number = a 168 elif o in ('-d', '--dir'): 169 self.session_dir = a 170 elif o in ('-v', '--verbose'): 171 self.verbose = True 172 173 if len(args) == 0: 174 print '* A command must be specified.' 175 return 2 176 177 command = args[0] 178 command_args = args[1:] 179 180 self.adb = Adb(serial_number) 181 182 if command == 'setup': 183 rc = self.do_setup(command_args) 184 elif command == 'shutdown': 185 rc = self.do_shutdown(command_args) 186 elif command == 'start': 187 rc = self.do_start(command_args) 188 elif command == 'stop': 189 rc = self.do_stop(command_args) 190 elif command == 'status': 191 rc = self.do_status(command_args) 192 elif command == 'import': 193 rc = self.do_import(command_args) 194 elif command == 'report': 195 rc = self.do_report(command_args) 196 elif command == 'annotate': 197 rc = self.do_annotate(command_args) 198 else: 199 print '* Unknown command: ' + command 200 return 2 201 202 return rc 203 204 def do_setup(self, command_args): 205 events = [] 206 timer = False 207 kernel = False 208 kernel_image = '' 209 callgraph = None 210 211 try: 212 opts, args = getopt.getopt(command_args, 213 'te:c:k:', ['timer', 'event=', 'callgraph=', 'kernel=']) 214 except getopt.GetoptError, e: 215 print '* Unsupported setup command arguments:', str(e) 216 return 2 217 218 for o, a in opts: 219 if o in ('-t', '--timer'): 220 timer = True 221 elif o in ('-e', '--event'): 222 events.append('--event=' + a) 223 elif o in ('-c', '--callgraph'): 224 callgraph = a 225 elif o in ('-k', '--kernel'): 226 kernel = True 227 kernel_image = a 228 229 if len(args) != 0: 230 print '* Unsupported setup command arguments: %s' % (' '.join(args)) 231 return 2 232 233 if not timer and len(events) == 0: 234 print '* Must specify --timer or at least one --event argument.' 235 return 2 236 237 if timer and len(events) != 0: 238 print '* --timer and --event cannot be used together.' 239 return 2 240 241 opcontrol_args = events 242 if timer: 243 opcontrol_args.append('--timer') 244 if callgraph is not None: 245 opcontrol_args.append('--callgraph=' + callgraph) 246 if kernel and len(kernel_image) != 0: 247 opcontrol_args.append('--vmlinux=' + kernel_image) 248 249 # Get kernal VMA range. 250 rc, output = self.adb.shell(['cat', '/proc/kallsyms'], echo=False) 251 if rc != 0: 252 print '* Failed to determine kernel VMA range.' 253 print output 254 return 1 255 vma_start = re.search('([0-9a-fA-F]{8}) T _text', output).group(1) 256 vma_end = re.search('([0-9a-fA-F]{8}) A _etext', output).group(1) 257 258 # Setup the profiler. 259 rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [ 260 '--reset', 261 '--kernel-range=' + vma_start + ',' + vma_end] + opcontrol_args + [ 262 '--setup', 263 '--status', '--verbose-log=all']) 264 if rc != 0: 265 print '* Failed to setup profiler.' 266 return 1 267 return 0 268 269 def do_shutdown(self, command_args): 270 if len(command_args) != 0: 271 print '* Unsupported shutdown command arguments: %s' % (' '.join(command_args)) 272 return 2 273 274 rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [ 275 '--shutdown']) 276 if rc != 0: 277 print '* Failed to shutdown.' 278 return 1 279 return 0 280 281 def do_start(self, command_args): 282 if len(command_args) != 0: 283 print '* Unsupported start command arguments: %s' % (' '.join(command_args)) 284 return 2 285 286 rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [ 287 '--start', '--status']) 288 if rc != 0: 289 print '* Failed to start profiler.' 290 return 1 291 return 0 292 293 def do_stop(self, command_args): 294 if len(command_args) != 0: 295 print '* Unsupported stop command arguments: %s' % (' '.join(command_args)) 296 return 2 297 298 rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [ 299 '--stop', '--status']) 300 if rc != 0: 301 print '* Failed to stop profiler.' 302 return 1 303 return 0 304 305 def do_status(self, command_args): 306 if len(command_args) != 0: 307 print '* Unsupported status command arguments: %s' % (' '.join(command_args)) 308 return 2 309 310 rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [ 311 '--status']) 312 if rc != 0: 313 print '* Failed to get profiler status.' 314 return 1 315 return 0 316 317 def do_import(self, command_args): 318 force = False 319 320 try: 321 opts, args = getopt.getopt(command_args, 322 'f', ['force']) 323 except getopt.GetoptError, e: 324 print '* Unsupported import command arguments:', str(e) 325 return 2 326 327 for o, a in opts: 328 if o in ('-f', '--force'): 329 force = True 330 331 if len(args) != 0: 332 print '* Unsupported import command arguments: %s' % (' '.join(args)) 333 return 2 334 335 # Create session directory. 336 print 'Creating session directory.' 337 if os.path.exists(self.session_dir): 338 if not force: 339 print "* Session directory already exists: %s" % (self.session_dir) 340 print "* Use --force to remove and recreate the session directory." 341 return 1 342 343 try: 344 shutil.rmtree(self.session_dir) 345 except e: 346 print "* Failed to remove existing session directory: %s" % (self.session_dir) 347 print e 348 return 1 349 350 try: 351 os.makedirs(self.session_dir) 352 except e: 353 print "* Failed to create session directory: %s" % (self.session_dir) 354 print e 355 return 1 356 357 raw_samples_dir = os.path.join(self.session_dir, 'raw_samples') 358 samples_dir = os.path.join(self.session_dir, 'samples') 359 abi_file = os.path.join(self.session_dir, 'abi') 360 361 # Dump samples. 362 print 'Dumping samples.' 363 rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [ 364 '--dump', '--status']) 365 if rc != 0: 366 print '* Failed to dump samples.' 367 print output 368 return 1 369 370 # Pull samples. 371 print 'Pulling samples from device.' 372 rc, output = self.adb.pull('/data/oprofile/samples/', raw_samples_dir + '/', echo=False) 373 if rc != 0: 374 print '* Failed to pull samples from the device.' 375 print output 376 return 1 377 378 # Pull ABI. 379 print 'Pulling ABI information from device.' 380 rc, output = self.adb.pull('/data/oprofile/abi', abi_file, echo=False) 381 if rc != 0: 382 print '* Failed to pull abi information from the device.' 383 print output 384 return 1 385 386 # Invoke opimport on each sample file to convert it from the device ABI (ARM) 387 # to the host ABI (x86). 388 print 'Importing samples.' 389 for dirpath, dirnames, filenames in os.walk(raw_samples_dir): 390 for filename in filenames: 391 if not re.match('^.*\.log$', filename): 392 in_path = os.path.join(dirpath, filename) 393 out_path = os.path.join(samples_dir, os.path.relpath(in_path, raw_samples_dir)) 394 out_dir = os.path.dirname(out_path) 395 try: 396 os.makedirs(out_dir) 397 except e: 398 print "* Failed to create sample directory: %s" % (out_dir) 399 print e 400 return 1 401 402 rc, output = execute([opimport_bin, '-a', abi_file, '-o', out_path, in_path], echo=False) 403 if rc != 0: 404 print '* Failed to import samples.' 405 print output 406 return 1 407 408 # Generate a short summary report. 409 rc, output = self._execute_opreport([]) 410 if rc != 0: 411 print '* Failed to generate summary report.' 412 return 1 413 return 0 414 415 def do_report(self, command_args): 416 rc, output = self._execute_opreport(command_args) 417 if rc != 0: 418 print '* Failed to generate report.' 419 return 1 420 return 0 421 422 def do_annotate(self, command_args): 423 rc, output = self._execute_opannotate(command_args) 424 if rc != 0: 425 print '* Failed to generate annotation.' 426 return 1 427 return 0 428 429 def _opcontrol_verbose_arg(self): 430 if self.verbose: 431 return ['--verbose'] 432 else: 433 return [] 434 435 def _execute_opreport(self, args): 436 return execute([opreport_bin, 437 '--session-dir=' + self.session_dir, 438 '--image-path=' + symbols_dir + ',' + system_dir] + args) 439 440 def _execute_opannotate(self, command_args): 441 try: 442 opts, args = getopt.getopt(command_args, 'sap:', 443 ['source', 'assembly', 'help', 'image-path=']) 444 except getopt.GetoptError, e: 445 print '* Unsupported opannotate command arguments:', str(e) 446 return 2 447 448 # Start with the default symbols directory 449 symbols_dirs = symbols_dir 450 451 anno_flag = [] 452 for o, a in opts: 453 if o in ('-s', '--source'): 454 anno_flag.append('-s') 455 if o in ('-a', '--assembly'): 456 anno_flag.append('-a') 457 anno_flag.append('--objdump-params=-Cd') 458 if o in ('--help'): 459 anno_flag.append('--help') 460 if o in ('p', '--image-path'): 461 symbols_dirs = a + ',' + symbols_dir 462 463 return execute([opannotate_bin, 464 '--session-dir=' + self.session_dir, 465 '--image-path=' + symbols_dirs + ',' + system_dir] + anno_flag + args) 466 467# Main entry point 468tool = Tool(sys.argv) 469rc = tool.main() 470sys.exit(rc) 471