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 
     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