blob: 85274997858327def7aaa2095e570211f3b3b273 [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')
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
Jeff Brown87866d92011-02-02 13:27:17 -080077class 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):
Jeff Brown220b7ff2011-05-03 15:33:24 -070085 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)
Jeff Brown87866d92011-02-02 13:27:17 -080092
93
Jeff Brown220b7ff2011-05-03 15:33:24 -070094# The tool program itself
Jeff Brown87866d92011-02-02 13:27:17 -080095class Tool:
96 def __init__(self, argv):
97 self.argv = argv
98 self.verbose = False
Jeff Brown220b7ff2011-05-03 15:33:24 -070099 self.session_dir = '/tmp/oprofile'
Jeff Brown87866d92011-02-02 13:27:17 -0800100
101 def usage(self):
Jeff Brown220b7ff2011-05-03 15:33:24 -0700102 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)"
Narayan Kamatha2e215a2011-08-16 17:53:51 +0100119 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."
Jeff Brown220b7ff2011-05-03 15:33:24 -0700122 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"
Jeff Brown87866d92011-02-02 13:27:17 -0800138 print
139
140 def main(self):
Jeff Brown220b7ff2011-05-03 15:33:24 -0700141 rc = self.do_main()
142 if rc == 2:
143 print
144 self.usage()
145 return rc
146
147 def do_main(self):
Jeff Brown87866d92011-02-02 13:27:17 -0800148 try:
149 opts, args = getopt.getopt(self.argv[1:],
Jeff Brown220b7ff2011-05-03 15:33:24 -0700150 'hs:vd', ['help', 'serial=', 'dir=', 'verbose'])
Jeff Brown87866d92011-02-02 13:27:17 -0800151 except getopt.GetoptError, e:
Jeff Brown87866d92011-02-02 13:27:17 -0800152 print str(e)
Jeff Brown220b7ff2011-05-03 15:33:24 -0700153 return 2
Jeff Brown87866d92011-02-02 13:27:17 -0800154
155 serial_number = None
Jeff Brown87866d92011-02-02 13:27:17 -0800156 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
Jeff Brown220b7ff2011-05-03 15:33:24 -0700162 elif o in ('-d', '--dir'):
163 self.session_dir = a
Jeff Brown87866d92011-02-02 13:27:17 -0800164 elif o in ('-v', '--verbose'):
165 self.verbose = True
Jeff Brown220b7ff2011-05-03 15:33:24 -0700166
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:]
Jeff Brown87866d92011-02-02 13:27:17 -0800173
174 self.adb = Adb(serial_number)
175
176 if command == 'setup':
Jeff Brown220b7ff2011-05-03 15:33:24 -0700177 rc = self.do_setup(command_args)
Jeff Brown87866d92011-02-02 13:27:17 -0800178 elif command == 'shutdown':
Jeff Brown220b7ff2011-05-03 15:33:24 -0700179 rc = self.do_shutdown(command_args)
Jeff Brown87866d92011-02-02 13:27:17 -0800180 elif command == 'start':
Jeff Brown220b7ff2011-05-03 15:33:24 -0700181 rc = self.do_start(command_args)
Jeff Brown87866d92011-02-02 13:27:17 -0800182 elif command == 'stop':
Jeff Brown220b7ff2011-05-03 15:33:24 -0700183 rc = self.do_stop(command_args)
Jeff Brown87866d92011-02-02 13:27:17 -0800184 elif command == 'status':
Jeff Brown220b7ff2011-05-03 15:33:24 -0700185 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)
Jeff Brown87866d92011-02-02 13:27:17 -0800190 else:
Jeff Brown220b7ff2011-05-03 15:33:24 -0700191 print '* Unknown command: ' + command
192 return 2
193
Jeff Brown87866d92011-02-02 13:27:17 -0800194 return rc
195
Jeff Brown220b7ff2011-05-03 15:33:24 -0700196 def do_setup(self, command_args):
197 events = []
198 timer = False
Narayan Kamatha2e215a2011-08-16 17:53:51 +0100199 kernel = False
200 kernel_image = ''
Jeff Brown220b7ff2011-05-03 15:33:24 -0700201 callgraph = None
202
203 try:
204 opts, args = getopt.getopt(command_args,
Narayan Kamatha2e215a2011-08-16 17:53:51 +0100205 'te:c:k:', ['timer', 'event=', 'callgraph=', 'kernel='])
Jeff Brown220b7ff2011-05-03 15:33:24 -0700206 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
Narayan Kamatha2e215a2011-08-16 17:53:51 +0100217 elif o in ('-k', '--kernel'):
218 kernel = True
219 kernel_image = a
Jeff Brown220b7ff2011-05-03 15:33:24 -0700220
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)
Narayan Kamatha2e215a2011-08-16 17:53:51 +0100242 if kernel and len(kernel_image) != 0:
243 opcontrol_args.append('--vmlinux=' + kernel_image)
Jeff Brown220b7ff2011-05-03 15:33:24 -0700244
245 # Get kernal VMA range.
Jeff Brown87866d92011-02-02 13:27:17 -0800246 rc, output = self.adb.shell(['cat', '/proc/kallsyms'], echo=False)
247 if rc != 0:
Jeff Brown220b7ff2011-05-03 15:33:24 -0700248 print '* Failed to determine kernel VMA range.'
249 print output
250 return 1
Jeff Brown87866d92011-02-02 13:27:17 -0800251 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
Jeff Brown220b7ff2011-05-03 15:33:24 -0700254 # Setup the profiler.
255 rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [
Jeff Brown87866d92011-02-02 13:27:17 -0800256 '--reset',
Narayan Kamatha2e215a2011-08-16 17:53:51 +0100257 '--kernel-range=' + vma_start + ',' + vma_end] + opcontrol_args + [
Jeff Brown87866d92011-02-02 13:27:17 -0800258 '--setup',
259 '--status', '--verbose-log=all'])
Jeff Brown220b7ff2011-05-03 15:33:24 -0700260 if rc != 0:
261 print '* Failed to setup profiler.'
262 return 1
263 return 0
Jeff Brown87866d92011-02-02 13:27:17 -0800264
Jeff Brown220b7ff2011-05-03 15:33:24 -0700265 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() + [
Jeff Brown87866d92011-02-02 13:27:17 -0800271 '--shutdown'])
272 if rc != 0:
Jeff Brown220b7ff2011-05-03 15:33:24 -0700273 print '* Failed to shutdown.'
274 return 1
275 return 0
Jeff Brown87866d92011-02-02 13:27:17 -0800276
Jeff Brown220b7ff2011-05-03 15:33:24 -0700277 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() + [
Jeff Brown87866d92011-02-02 13:27:17 -0800283 '--start', '--status'])
Jeff Brown220b7ff2011-05-03 15:33:24 -0700284 if rc != 0:
285 print '* Failed to start profiler.'
286 return 1
287 return 0
Jeff Brown87866d92011-02-02 13:27:17 -0800288
Jeff Brown220b7ff2011-05-03 15:33:24 -0700289 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() + [
Jeff Brown87866d92011-02-02 13:27:17 -0800295 '--stop', '--status'])
Jeff Brown220b7ff2011-05-03 15:33:24 -0700296 if rc != 0:
297 print '* Failed to stop profiler.'
298 return 1
299 return 0
Jeff Brown87866d92011-02-02 13:27:17 -0800300
Jeff Brown220b7ff2011-05-03 15:33:24 -0700301 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() + [
Jeff Brown87866d92011-02-02 13:27:17 -0800307 '--status'])
Jeff Brown220b7ff2011-05-03 15:33:24 -0700308 if rc != 0:
309 print '* Failed to get profiler status.'
310 return 1
311 return 0
Jeff Brown87866d92011-02-02 13:27:17 -0800312
Jeff Brown220b7ff2011-05-03 15:33:24 -0700313 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):
Jeff Brown87866d92011-02-02 13:27:17 -0800419 if self.verbose:
420 return ['--verbose']
421 else:
422 return []
423
Jeff Brown220b7ff2011-05-03 15:33:24 -0700424 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
Jeff Brown87866d92011-02-02 13:27:17 -0800429# Main entry point
430tool = Tool(sys.argv)
431rc = tool.main()
Jeff Brownc3efb232011-03-17 16:08:14 -0700432sys.exit(rc)