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