blob: efdaddea5c22525ecc9701cf5bfc287b078aae86 [file] [log] [blame]
#!/usr/bin/env python2.6
#
# Copyright (C) 2011 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
# Remotely controls an OProfile session on an Android device.
#
import os
import sys
import subprocess
import getopt
import re
import shutil
# Find oprofile binaries (compiled on the host)
try:
oprofile_bin_dir = os.environ['OPROFILE_BIN_DIR']
except:
try:
android_host_out = os.environ['ANDROID_HOST_OUT']
except:
print "Either OPROFILE_BIN_DIR or ANDROID_HOST_OUT must be set. Run \". envsetup.sh\" first"
sys.exit(1)
oprofile_bin_dir = os.path.join(android_host_out, 'bin')
opimport_bin = os.path.join(oprofile_bin_dir, 'opimport')
opreport_bin = os.path.join(oprofile_bin_dir, 'opreport')
opannotate_bin = os.path.join(oprofile_bin_dir, 'opannotate')
# Find symbol directories
try:
android_product_out = os.environ['ANDROID_PRODUCT_OUT']
except:
print "ANDROID_PRODUCT_OUT must be set. Run \". envsetup.sh\" first"
sys.exit(1)
symbols_dir = os.path.join(android_product_out, 'symbols')
system_dir = os.path.join(android_product_out, 'system')
def execute(command, echo=True):
if echo:
print ' '.join(command)
popen = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output = ''
while True:
stdout, stderr = popen.communicate()
if echo and len(stdout) != 0:
print stdout
if echo and len(stderr) != 0:
print stderr
output += stdout
output += stderr
rc = popen.poll()
if rc is not None:
break
if echo:
print 'exit code: %d' % rc
return rc, output
# ADB wrapper
class Adb:
def __init__(self, serial_number):
self._base_args = ['adb']
if serial_number != None:
self._base_args.append('-s')
self._base_args.append(serial_number)
def shell(self, command_args, echo=True):
return self._adb('shell', command_args, echo)
def pull(self, source, dest, echo=True):
return self._adb('pull', [source, dest], echo)
def _adb(self, command, command_args, echo):
return execute(self._base_args + [command] + command_args, echo)
# The tool program itself
class Tool:
def __init__(self, argv):
self.argv = argv
self.verbose = False
self.session_dir = '/tmp/oprofile'
def usage(self):
print "Usage: " + self.argv[0] + " [options] <command> [command args]"
print
print " Options:"
print
print " -h, --help : show this help text"
print " -s, --serial=number : the serial number of the device being profiled"
print " -v, --verbose : show verbose output"
print " -d, --dir=path : directory to store oprofile session on the host, default: /tmp/oprofile"
print
print " Commands:"
print
print " setup [args] : setup profiler with specified arguments to 'opcontrol --setup'"
print " -t, --timer : enable timer based profiling"
print " -e, --event=[spec] : specify an event type to profile, eg. --event=CPU_CYCLES:100000"
print " (not supported on all devices)"
print " -c, --callgraph=[depth] : specify callgraph capture depth, default is none"
print " (not supported in timer mode)"
print " -k, --kernel-image : specifies the location of a kernel image relative to the symbols directory"
print " (and turns on kernel profiling). This need not be the same as the"
print " location of the kernel on the actual device."
print
print " shutdown : shutdown profiler"
print
print " start : start profiling"
print
print " stop : stop profiling"
print
print " status : show profiler status"
print
print " import : dump samples and pull session directory from the device"
print " -f, --force : remove existing session directory before import"
print
print " report [args] : generate report with specified arguments to 'opreport'"
print " -l, --symbols : show symbols"
print " -c, --callgraph : show callgraph"
print " --help : show help for additional opreport options"
print
print " annotate [args] : generate annotation with specified arguments to 'annotation'"
print " -s, --source : show source"
print " -a, --assembly : show assembly"
print " --help : show help for additional opannotate options"
print
def main(self):
rc = self.do_main()
if rc == 2:
print
self.usage()
return rc
def do_main(self):
try:
opts, args = getopt.getopt(self.argv[1:],
'hs:vd', ['help', 'serial=', 'dir=', 'verbose'])
except getopt.GetoptError, e:
print str(e)
return 2
serial_number = None
for o, a in opts:
if o in ('-h', '--help'):
self.usage()
return 0
elif o in ('-s', '--serial'):
serial_number = a
elif o in ('-d', '--dir'):
self.session_dir = a
elif o in ('-v', '--verbose'):
self.verbose = True
if len(args) == 0:
print '* A command must be specified.'
return 2
command = args[0]
command_args = args[1:]
self.adb = Adb(serial_number)
if command == 'setup':
rc = self.do_setup(command_args)
elif command == 'shutdown':
rc = self.do_shutdown(command_args)
elif command == 'start':
rc = self.do_start(command_args)
elif command == 'stop':
rc = self.do_stop(command_args)
elif command == 'status':
rc = self.do_status(command_args)
elif command == 'import':
rc = self.do_import(command_args)
elif command == 'report':
rc = self.do_report(command_args)
elif command == 'annotate':
rc = self.do_annotate(command_args)
else:
print '* Unknown command: ' + command
return 2
return rc
def do_setup(self, command_args):
events = []
timer = False
kernel = False
kernel_image = ''
callgraph = None
try:
opts, args = getopt.getopt(command_args,
'te:c:k:', ['timer', 'event=', 'callgraph=', 'kernel='])
except getopt.GetoptError, e:
print '* Unsupported setup command arguments:', str(e)
return 2
for o, a in opts:
if o in ('-t', '--timer'):
timer = True
elif o in ('-e', '--event'):
events.append('--event=' + a)
elif o in ('-c', '--callgraph'):
callgraph = a
elif o in ('-k', '--kernel'):
kernel = True
kernel_image = a
if len(args) != 0:
print '* Unsupported setup command arguments: %s' % (' '.join(args))
return 2
if not timer and len(events) == 0:
print '* Must specify --timer or at least one --event argument.'
return 2
if timer and len(events) != 0:
print '* --timer and --event cannot be used together.'
return 2
opcontrol_args = events
if timer:
opcontrol_args.append('--timer')
if callgraph is not None:
opcontrol_args.append('--callgraph=' + callgraph)
if kernel and len(kernel_image) != 0:
opcontrol_args.append('--vmlinux=' + kernel_image)
# Get kernal VMA range.
rc, output = self.adb.shell(['cat', '/proc/kallsyms'], echo=False)
if rc != 0:
print '* Failed to determine kernel VMA range.'
print output
return 1
vma_start = re.search('([0-9a-fA-F]{8}) T _text', output).group(1)
vma_end = re.search('([0-9a-fA-F]{8}) A _etext', output).group(1)
# Setup the profiler.
rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [
'--reset',
'--kernel-range=' + vma_start + ',' + vma_end] + opcontrol_args + [
'--setup',
'--status', '--verbose-log=all'])
if rc != 0:
print '* Failed to setup profiler.'
return 1
return 0
def do_shutdown(self, command_args):
if len(command_args) != 0:
print '* Unsupported shutdown command arguments: %s' % (' '.join(command_args))
return 2
rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [
'--shutdown'])
if rc != 0:
print '* Failed to shutdown.'
return 1
return 0
def do_start(self, command_args):
if len(command_args) != 0:
print '* Unsupported start command arguments: %s' % (' '.join(command_args))
return 2
rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [
'--start', '--status'])
if rc != 0:
print '* Failed to start profiler.'
return 1
return 0
def do_stop(self, command_args):
if len(command_args) != 0:
print '* Unsupported stop command arguments: %s' % (' '.join(command_args))
return 2
rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [
'--stop', '--status'])
if rc != 0:
print '* Failed to stop profiler.'
return 1
return 0
def do_status(self, command_args):
if len(command_args) != 0:
print '* Unsupported status command arguments: %s' % (' '.join(command_args))
return 2
rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [
'--status'])
if rc != 0:
print '* Failed to get profiler status.'
return 1
return 0
def do_import(self, command_args):
force = False
try:
opts, args = getopt.getopt(command_args,
'f', ['force'])
except getopt.GetoptError, e:
print '* Unsupported import command arguments:', str(e)
return 2
for o, a in opts:
if o in ('-f', '--force'):
force = True
if len(args) != 0:
print '* Unsupported import command arguments: %s' % (' '.join(args))
return 2
# Create session directory.
print 'Creating session directory.'
if os.path.exists(self.session_dir):
if not force:
print "* Session directory already exists: %s" % (self.session_dir)
print "* Use --force to remove and recreate the session directory."
return 1
try:
shutil.rmtree(self.session_dir)
except e:
print "* Failed to remove existing session directory: %s" % (self.session_dir)
print e
return 1
try:
os.makedirs(self.session_dir)
except e:
print "* Failed to create session directory: %s" % (self.session_dir)
print e
return 1
raw_samples_dir = os.path.join(self.session_dir, 'raw_samples')
samples_dir = os.path.join(self.session_dir, 'samples')
abi_file = os.path.join(self.session_dir, 'abi')
# Dump samples.
print 'Dumping samples.'
rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [
'--dump', '--status'])
if rc != 0:
print '* Failed to dump samples.'
print output
return 1
# Pull samples.
print 'Pulling samples from device.'
rc, output = self.adb.pull('/data/oprofile/samples/', raw_samples_dir + '/', echo=False)
if rc != 0:
print '* Failed to pull samples from the device.'
print output
return 1
# Pull ABI.
print 'Pulling ABI information from device.'
rc, output = self.adb.pull('/data/oprofile/abi', abi_file, echo=False)
if rc != 0:
print '* Failed to pull abi information from the device.'
print output
return 1
# Invoke opimport on each sample file to convert it from the device ABI (ARM)
# to the host ABI (x86).
print 'Importing samples.'
for dirpath, dirnames, filenames in os.walk(raw_samples_dir):
for filename in filenames:
if not re.match('^.*\.log$', filename):
in_path = os.path.join(dirpath, filename)
out_path = os.path.join(samples_dir, os.path.relpath(in_path, raw_samples_dir))
out_dir = os.path.dirname(out_path)
try:
os.makedirs(out_dir)
except e:
print "* Failed to create sample directory: %s" % (out_dir)
print e
return 1
rc, output = execute([opimport_bin, '-a', abi_file, '-o', out_path, in_path], echo=False)
if rc != 0:
print '* Failed to import samples.'
print output
return 1
# Generate a short summary report.
rc, output = self._execute_opreport([])
if rc != 0:
print '* Failed to generate summary report.'
return 1
return 0
def do_report(self, command_args):
rc, output = self._execute_opreport(command_args)
if rc != 0:
print '* Failed to generate report.'
return 1
return 0
def do_annotate(self, command_args):
rc, output = self._execute_opannotate(command_args)
if rc != 0:
print '* Failed to generate annotation.'
return 1
return 0
def _opcontrol_verbose_arg(self):
if self.verbose:
return ['--verbose']
else:
return []
def _execute_opreport(self, args):
return execute([opreport_bin,
'--session-dir=' + self.session_dir,
'--image-path=' + symbols_dir + ',' + system_dir] + args)
def _execute_opannotate(self, command_args):
try:
opts, args = getopt.getopt(command_args, 'sap:',
['source', 'assembly', 'help', 'image-path='])
except getopt.GetoptError, e:
print '* Unsupported opannotate command arguments:', str(e)
return 2
# Start with the default symbols directory
symbols_dirs = symbols_dir
anno_flag = []
for o, a in opts:
if o in ('-s', '--source'):
anno_flag.append('-s')
if o in ('-a', '--assembly'):
anno_flag.append('-a')
anno_flag.append('--objdump-params=-Cd')
if o in ('--help'):
anno_flag.append('--help')
if o in ('p', '--image-path'):
symbols_dirs = a + ',' + symbols_dir
return execute([opannotate_bin,
'--session-dir=' + self.session_dir,
'--image-path=' + symbols_dirs + ',' + system_dir] + anno_flag + args)
# Main entry point
tool = Tool(sys.argv)
rc = tool.main()
sys.exit(rc)