oprofile_android revision 220b7fffe31541dc33e8a9bb297128dd46567e74
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') 43 44 45# Find symbol directories 46try: 47 android_product_out = os.environ['ANDROID_PRODUCT_OUT'] 48except: 49 print "ANDROID_PRODUCT_OUT must be set. Run \". envsetup.sh\" first" 50 sys.exit(1) 51 52symbols_dir = os.path.join(android_product_out, 'symbols') 53system_dir = os.path.join(android_product_out, 'system') 54 55 56def 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 77class 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 95class 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 120 print " shutdown : shutdown profiler" 121 print 122 print " start : start profiling" 123 print 124 print " stop : stop profiling" 125 print 126 print " status : show profiler status" 127 print 128 print " import : dump samples and pull session directory from the device" 129 print " -f, --force : remove existing session directory before import" 130 print 131 print " report [args] : generate report with specified arguments to 'opreport'" 132 print " -l, --symbols : show symbols" 133 print " -c, --callgraph : show callgraph" 134 print " --help : show help for additional opreport options" 135 print 136 137 def main(self): 138 rc = self.do_main() 139 if rc == 2: 140 print 141 self.usage() 142 return rc 143 144 def do_main(self): 145 try: 146 opts, args = getopt.getopt(self.argv[1:], 147 'hs:vd', ['help', 'serial=', 'dir=', 'verbose']) 148 except getopt.GetoptError, e: 149 print str(e) 150 return 2 151 152 serial_number = None 153 for o, a in opts: 154 if o in ('-h', '--help'): 155 self.usage() 156 return 0 157 elif o in ('-s', '--serial'): 158 serial_number = a 159 elif o in ('-d', '--dir'): 160 self.session_dir = a 161 elif o in ('-v', '--verbose'): 162 self.verbose = True 163 164 if len(args) == 0: 165 print '* A command must be specified.' 166 return 2 167 168 command = args[0] 169 command_args = args[1:] 170 171 self.adb = Adb(serial_number) 172 173 if command == 'setup': 174 rc = self.do_setup(command_args) 175 elif command == 'shutdown': 176 rc = self.do_shutdown(command_args) 177 elif command == 'start': 178 rc = self.do_start(command_args) 179 elif command == 'stop': 180 rc = self.do_stop(command_args) 181 elif command == 'status': 182 rc = self.do_status(command_args) 183 elif command == 'import': 184 rc = self.do_import(command_args) 185 elif command == 'report': 186 rc = self.do_report(command_args) 187 else: 188 print '* Unknown command: ' + command 189 return 2 190 191 return rc 192 193 def do_setup(self, command_args): 194 events = [] 195 timer = False 196 callgraph = None 197 198 try: 199 opts, args = getopt.getopt(command_args, 200 'te:c:', ['timer', 'event=', 'callgraph=']) 201 except getopt.GetoptError, e: 202 print '* Unsupported setup command arguments:', str(e) 203 return 2 204 205 for o, a in opts: 206 if o in ('-t', '--timer'): 207 timer = True 208 elif o in ('-e', '--event'): 209 events.append('--event=' + a) 210 elif o in ('-c', '--callgraph'): 211 callgraph = a 212 213 if len(args) != 0: 214 print '* Unsupported setup command arguments: %s' % (' '.join(args)) 215 return 2 216 217 if not timer and len(events) == 0: 218 print '* Must specify --timer or at least one --event argument.' 219 return 2 220 221 if timer and len(events) != 0: 222 print '* --timer and --event cannot be used together.' 223 return 2 224 225 if timer and callgraph is not None: 226 print '* --callgraph cannot be used with --timer.' 227 return 2 228 229 opcontrol_args = events 230 if timer: 231 opcontrol_args.append('--timer') 232 if callgraph is not None: 233 opcontrol_args.append('--callgraph=' + callgraph) 234 235 # Get kernal VMA range. 236 rc, output = self.adb.shell(['cat', '/proc/kallsyms'], echo=False) 237 if rc != 0: 238 print '* Failed to determine kernel VMA range.' 239 print output 240 return 1 241 vma_start = re.search('([0-9a-fA-F]{8}) T _text', output).group(1) 242 vma_end = re.search('([0-9a-fA-F]{8}) A _etext', output).group(1) 243 244 # Setup the profiler. 245 rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [ 246 '--reset', 247 '--kernel-range=' + vma_start + '-' + vma_end] + opcontrol_args + [ 248 '--setup', 249 '--status', '--verbose-log=all']) 250 if rc != 0: 251 print '* Failed to setup profiler.' 252 return 1 253 return 0 254 255 def do_shutdown(self, command_args): 256 if len(command_args) != 0: 257 print '* Unsupported shutdown command arguments: %s' % (' '.join(command_args)) 258 return 2 259 260 rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [ 261 '--shutdown']) 262 if rc != 0: 263 print '* Failed to shutdown.' 264 return 1 265 return 0 266 267 def do_start(self, command_args): 268 if len(command_args) != 0: 269 print '* Unsupported start command arguments: %s' % (' '.join(command_args)) 270 return 2 271 272 rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [ 273 '--start', '--status']) 274 if rc != 0: 275 print '* Failed to start profiler.' 276 return 1 277 return 0 278 279 def do_stop(self, command_args): 280 if len(command_args) != 0: 281 print '* Unsupported stop command arguments: %s' % (' '.join(command_args)) 282 return 2 283 284 rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [ 285 '--stop', '--status']) 286 if rc != 0: 287 print '* Failed to stop profiler.' 288 return 1 289 return 0 290 291 def do_status(self, command_args): 292 if len(command_args) != 0: 293 print '* Unsupported status command arguments: %s' % (' '.join(command_args)) 294 return 2 295 296 rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [ 297 '--status']) 298 if rc != 0: 299 print '* Failed to get profiler status.' 300 return 1 301 return 0 302 303 def do_import(self, command_args): 304 force = False 305 306 try: 307 opts, args = getopt.getopt(command_args, 308 'f', ['force']) 309 except getopt.GetoptError, e: 310 print '* Unsupported import command arguments:', str(e) 311 return 2 312 313 for o, a in opts: 314 if o in ('-f', '--force'): 315 force = True 316 317 if len(args) != 0: 318 print '* Unsupported import command arguments: %s' % (' '.join(args)) 319 return 2 320 321 # Create session directory. 322 print 'Creating session directory.' 323 if os.path.exists(self.session_dir): 324 if not force: 325 print "* Session directory already exists: %s" % (self.session_dir) 326 print "* Use --force to remove and recreate the session directory." 327 return 1 328 329 try: 330 shutil.rmtree(self.session_dir) 331 except e: 332 print "* Failed to remove existing session directory: %s" % (self.session_dir) 333 print e 334 return 1 335 336 try: 337 os.makedirs(self.session_dir) 338 except e: 339 print "* Failed to create session directory: %s" % (self.session_dir) 340 print e 341 return 1 342 343 raw_samples_dir = os.path.join(self.session_dir, 'raw_samples') 344 samples_dir = os.path.join(self.session_dir, 'samples') 345 abi_file = os.path.join(self.session_dir, 'abi') 346 347 # Dump samples. 348 print 'Dumping samples.' 349 rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [ 350 '--dump', '--status']) 351 if rc != 0: 352 print '* Failed to dump samples.' 353 print output 354 return 1 355 356 # Pull samples. 357 print 'Pulling samples from device.' 358 rc, output = self.adb.pull('/data/oprofile/samples/', raw_samples_dir + '/', echo=False) 359 if rc != 0: 360 print '* Failed to pull samples from the device.' 361 print output 362 return 1 363 364 # Pull ABI. 365 print 'Pulling ABI information from device.' 366 rc, output = self.adb.pull('/data/oprofile/abi', abi_file, echo=False) 367 if rc != 0: 368 print '* Failed to pull abi information from the device.' 369 print output 370 return 1 371 372 # Invoke opimport on each sample file to convert it from the device ABI (ARM) 373 # to the host ABI (x86). 374 print 'Importing samples.' 375 for dirpath, dirnames, filenames in os.walk(raw_samples_dir): 376 for filename in filenames: 377 if not re.match('^.*\.log$', filename): 378 in_path = os.path.join(dirpath, filename) 379 out_path = os.path.join(samples_dir, os.path.relpath(in_path, raw_samples_dir)) 380 out_dir = os.path.dirname(out_path) 381 try: 382 os.makedirs(out_dir) 383 except e: 384 print "* Failed to create sample directory: %s" % (out_dir) 385 print e 386 return 1 387 388 rc, output = execute([opimport_bin, '-a', abi_file, '-o', out_path, in_path], echo=False) 389 if rc != 0: 390 print '* Failed to import samples.' 391 print output 392 return 1 393 394 # Generate a short summary report. 395 rc, output = self._execute_opreport([]) 396 if rc != 0: 397 print '* Failed to generate summary report.' 398 return 1 399 return 0 400 401 def do_report(self, command_args): 402 rc, output = self._execute_opreport(command_args) 403 if rc != 0: 404 print '* Failed to generate report.' 405 return 1 406 return 0 407 408 def _opcontrol_verbose_arg(self): 409 if self.verbose: 410 return ['--verbose'] 411 else: 412 return [] 413 414 def _execute_opreport(self, args): 415 return execute([opreport_bin, 416 '--session-dir=' + self.session_dir, 417 '--image-path=' + symbols_dir + ',' + system_dir] + args) 418 419# Main entry point 420tool = Tool(sys.argv) 421rc = tool.main() 422sys.exit(rc) 423