blob: efdaddea5c22525ecc9701cf5bfc287b078aae86 [file] [log] [blame]
Jeff Brown87866d92011-02-02 13:27:17 -08001#!/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
Jeff Brown220b7ff2011-05-03 15:33:24 -070027import shutil
Jeff Brown87866d92011-02-02 13:27:17 -080028
29
Jeff Brown220b7ff2011-05-03 15:33:24 -070030# 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')
Ben Chenge4b944c2012-01-06 15:35:28 -080043opannotate_bin = os.path.join(oprofile_bin_dir, 'opannotate')
Jeff Brown220b7ff2011-05-03 15:33:24 -070044
45
46# Find symbol directories
47try:
48 android_product_out = os.environ['ANDROID_PRODUCT_OUT']
49except:
50 print "ANDROID_PRODUCT_OUT must be set. Run \". envsetup.sh\" first"
51 sys.exit(1)
52
53symbols_dir = os.path.join(android_product_out, 'symbols')
54system_dir = os.path.join(android_product_out, 'system')
55
56
57def 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
Jeff Brown87866d92011-02-02 13:27:17 -080078class 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):
Jeff Brown220b7ff2011-05-03 15:33:24 -070086 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)
Jeff Brown87866d92011-02-02 13:27:17 -080093
94
Jeff Brown220b7ff2011-05-03 15:33:24 -070095# The tool program itself
Jeff Brown87866d92011-02-02 13:27:17 -080096class Tool:
97 def __init__(self, argv):
98 self.argv = argv
99 self.verbose = False
Jeff Brown220b7ff2011-05-03 15:33:24 -0700100 self.session_dir = '/tmp/oprofile'
Jeff Brown87866d92011-02-02 13:27:17 -0800101
102 def usage(self):
Jeff Brown220b7ff2011-05-03 15:33:24 -0700103 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)"
Narayan Kamatha2e215a2011-08-16 17:53:51 +0100120 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."
Jeff Brown220b7ff2011-05-03 15:33:24 -0700123 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"
Jeff Brown87866d92011-02-02 13:27:17 -0800139 print
Ben Chenge4b944c2012-01-06 15:35:28 -0800140 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
Jeff Brown87866d92011-02-02 13:27:17 -0800145
146 def main(self):
Jeff Brown220b7ff2011-05-03 15:33:24 -0700147 rc = self.do_main()
148 if rc == 2:
149 print
150 self.usage()
151 return rc
152
153 def do_main(self):
Jeff Brown87866d92011-02-02 13:27:17 -0800154 try:
155 opts, args = getopt.getopt(self.argv[1:],
Jeff Brown220b7ff2011-05-03 15:33:24 -0700156 'hs:vd', ['help', 'serial=', 'dir=', 'verbose'])
Jeff Brown87866d92011-02-02 13:27:17 -0800157 except getopt.GetoptError, e:
Jeff Brown87866d92011-02-02 13:27:17 -0800158 print str(e)
Jeff Brown220b7ff2011-05-03 15:33:24 -0700159 return 2
Jeff Brown87866d92011-02-02 13:27:17 -0800160
161 serial_number = None
Jeff Brown87866d92011-02-02 13:27:17 -0800162 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
Jeff Brown220b7ff2011-05-03 15:33:24 -0700168 elif o in ('-d', '--dir'):
169 self.session_dir = a
Jeff Brown87866d92011-02-02 13:27:17 -0800170 elif o in ('-v', '--verbose'):
171 self.verbose = True
Jeff Brown220b7ff2011-05-03 15:33:24 -0700172
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:]
Jeff Brown87866d92011-02-02 13:27:17 -0800179
180 self.adb = Adb(serial_number)
181
182 if command == 'setup':
Jeff Brown220b7ff2011-05-03 15:33:24 -0700183 rc = self.do_setup(command_args)
Jeff Brown87866d92011-02-02 13:27:17 -0800184 elif command == 'shutdown':
Jeff Brown220b7ff2011-05-03 15:33:24 -0700185 rc = self.do_shutdown(command_args)
Jeff Brown87866d92011-02-02 13:27:17 -0800186 elif command == 'start':
Jeff Brown220b7ff2011-05-03 15:33:24 -0700187 rc = self.do_start(command_args)
Jeff Brown87866d92011-02-02 13:27:17 -0800188 elif command == 'stop':
Jeff Brown220b7ff2011-05-03 15:33:24 -0700189 rc = self.do_stop(command_args)
Jeff Brown87866d92011-02-02 13:27:17 -0800190 elif command == 'status':
Jeff Brown220b7ff2011-05-03 15:33:24 -0700191 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)
Ben Chenge4b944c2012-01-06 15:35:28 -0800196 elif command == 'annotate':
197 rc = self.do_annotate(command_args)
Jeff Brown87866d92011-02-02 13:27:17 -0800198 else:
Jeff Brown220b7ff2011-05-03 15:33:24 -0700199 print '* Unknown command: ' + command
200 return 2
201
Jeff Brown87866d92011-02-02 13:27:17 -0800202 return rc
203
Jeff Brown220b7ff2011-05-03 15:33:24 -0700204 def do_setup(self, command_args):
205 events = []
206 timer = False
Narayan Kamatha2e215a2011-08-16 17:53:51 +0100207 kernel = False
208 kernel_image = ''
Jeff Brown220b7ff2011-05-03 15:33:24 -0700209 callgraph = None
210
211 try:
212 opts, args = getopt.getopt(command_args,
Narayan Kamatha2e215a2011-08-16 17:53:51 +0100213 'te:c:k:', ['timer', 'event=', 'callgraph=', 'kernel='])
Jeff Brown220b7ff2011-05-03 15:33:24 -0700214 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
Narayan Kamatha2e215a2011-08-16 17:53:51 +0100225 elif o in ('-k', '--kernel'):
226 kernel = True
227 kernel_image = a
Jeff Brown220b7ff2011-05-03 15:33:24 -0700228
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
Jeff Brown220b7ff2011-05-03 15:33:24 -0700241 opcontrol_args = events
242 if timer:
243 opcontrol_args.append('--timer')
244 if callgraph is not None:
245 opcontrol_args.append('--callgraph=' + callgraph)
Narayan Kamatha2e215a2011-08-16 17:53:51 +0100246 if kernel and len(kernel_image) != 0:
247 opcontrol_args.append('--vmlinux=' + kernel_image)
Jeff Brown220b7ff2011-05-03 15:33:24 -0700248
249 # Get kernal VMA range.
Jeff Brown87866d92011-02-02 13:27:17 -0800250 rc, output = self.adb.shell(['cat', '/proc/kallsyms'], echo=False)
251 if rc != 0:
Jeff Brown220b7ff2011-05-03 15:33:24 -0700252 print '* Failed to determine kernel VMA range.'
253 print output
254 return 1
Jeff Brown87866d92011-02-02 13:27:17 -0800255 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
Jeff Brown220b7ff2011-05-03 15:33:24 -0700258 # Setup the profiler.
259 rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [
Jeff Brown87866d92011-02-02 13:27:17 -0800260 '--reset',
Narayan Kamatha2e215a2011-08-16 17:53:51 +0100261 '--kernel-range=' + vma_start + ',' + vma_end] + opcontrol_args + [
Jeff Brown87866d92011-02-02 13:27:17 -0800262 '--setup',
263 '--status', '--verbose-log=all'])
Jeff Brown220b7ff2011-05-03 15:33:24 -0700264 if rc != 0:
265 print '* Failed to setup profiler.'
266 return 1
267 return 0
Jeff Brown87866d92011-02-02 13:27:17 -0800268
Jeff Brown220b7ff2011-05-03 15:33:24 -0700269 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() + [
Jeff Brown87866d92011-02-02 13:27:17 -0800275 '--shutdown'])
276 if rc != 0:
Jeff Brown220b7ff2011-05-03 15:33:24 -0700277 print '* Failed to shutdown.'
278 return 1
279 return 0
Jeff Brown87866d92011-02-02 13:27:17 -0800280
Jeff Brown220b7ff2011-05-03 15:33:24 -0700281 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() + [
Jeff Brown87866d92011-02-02 13:27:17 -0800287 '--start', '--status'])
Jeff Brown220b7ff2011-05-03 15:33:24 -0700288 if rc != 0:
289 print '* Failed to start profiler.'
290 return 1
291 return 0
Jeff Brown87866d92011-02-02 13:27:17 -0800292
Jeff Brown220b7ff2011-05-03 15:33:24 -0700293 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() + [
Jeff Brown87866d92011-02-02 13:27:17 -0800299 '--stop', '--status'])
Jeff Brown220b7ff2011-05-03 15:33:24 -0700300 if rc != 0:
301 print '* Failed to stop profiler.'
302 return 1
303 return 0
Jeff Brown87866d92011-02-02 13:27:17 -0800304
Jeff Brown220b7ff2011-05-03 15:33:24 -0700305 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() + [
Jeff Brown87866d92011-02-02 13:27:17 -0800311 '--status'])
Jeff Brown220b7ff2011-05-03 15:33:24 -0700312 if rc != 0:
313 print '* Failed to get profiler status.'
314 return 1
315 return 0
Jeff Brown87866d92011-02-02 13:27:17 -0800316
Jeff Brown220b7ff2011-05-03 15:33:24 -0700317 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
Ben Chenge4b944c2012-01-06 15:35:28 -0800422 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
Jeff Brown220b7ff2011-05-03 15:33:24 -0700429 def _opcontrol_verbose_arg(self):
Jeff Brown87866d92011-02-02 13:27:17 -0800430 if self.verbose:
431 return ['--verbose']
432 else:
433 return []
434
Jeff Brown220b7ff2011-05-03 15:33:24 -0700435 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
Ben Chenge4b944c2012-01-06 15:35:28 -0800440 def _execute_opannotate(self, command_args):
441 try:
Ben Chengf0d3b7a2012-01-09 13:41:10 -0800442 opts, args = getopt.getopt(command_args, 'sap:',
443 ['source', 'assembly', 'help', 'image-path='])
Ben Chenge4b944c2012-01-06 15:35:28 -0800444 except getopt.GetoptError, e:
445 print '* Unsupported opannotate command arguments:', str(e)
446 return 2
447
Ben Chengf0d3b7a2012-01-09 13:41:10 -0800448 # Start with the default symbols directory
449 symbols_dirs = symbols_dir
450
Ben Chenge4b944c2012-01-06 15:35:28 -0800451 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')
Ben Chengf0d3b7a2012-01-09 13:41:10 -0800460 if o in ('p', '--image-path'):
461 symbols_dirs = a + ',' + symbols_dir
Ben Chenge4b944c2012-01-06 15:35:28 -0800462
463 return execute([opannotate_bin,
464 '--session-dir=' + self.session_dir,
Ben Chengf0d3b7a2012-01-09 13:41:10 -0800465 '--image-path=' + symbols_dirs + ',' + system_dir] + anno_flag + args)
Ben Chenge4b944c2012-01-06 15:35:28 -0800466
Jeff Brown87866d92011-02-02 13:27:17 -0800467# Main entry point
468tool = Tool(sys.argv)
469rc = tool.main()
Jeff Brownc3efb232011-03-17 16:08:14 -0700470sys.exit(rc)