Home | History | Annotate | Download | only in oprofile
      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 opannotate_bin = os.path.join(oprofile_bin_dir, 'opannotate')
     44 
     45 
     46 # Find symbol directories
     47 try:
     48   android_product_out = os.environ['ANDROID_PRODUCT_OUT']
     49 except:
     50   print "ANDROID_PRODUCT_OUT must be set. Run \". envsetup.sh\" first"
     51   sys.exit(1)
     52 
     53 symbols_dir = os.path.join(android_product_out, 'symbols')
     54 system_dir = os.path.join(android_product_out, 'system')
     55 
     56 
     57 def 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
     78 class 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
     96 class 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
    468 tool = Tool(sys.argv)
    469 rc = tool.main()
    470 sys.exit(rc)
    471