blob: 7716eac60ecbec54084a6dcfb320b6b362fe1f4e [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
Jeff Brown220b7ff2011-05-03 15:33:24 -0700233 opcontrol_args = events
234 if timer:
235 opcontrol_args.append('--timer')
236 if callgraph is not None:
237 opcontrol_args.append('--callgraph=' + callgraph)
Narayan Kamatha2e215a2011-08-16 17:53:51 +0100238 if kernel and len(kernel_image) != 0:
239 opcontrol_args.append('--vmlinux=' + kernel_image)
Jeff Brown220b7ff2011-05-03 15:33:24 -0700240
241 # Get kernal VMA range.
Jeff Brown87866d92011-02-02 13:27:17 -0800242 rc, output = self.adb.shell(['cat', '/proc/kallsyms'], echo=False)
243 if rc != 0:
Jeff Brown220b7ff2011-05-03 15:33:24 -0700244 print '* Failed to determine kernel VMA range.'
245 print output
246 return 1
Jeff Brown87866d92011-02-02 13:27:17 -0800247 vma_start = re.search('([0-9a-fA-F]{8}) T _text', output).group(1)
248 vma_end = re.search('([0-9a-fA-F]{8}) A _etext', output).group(1)
249
Jeff Brown220b7ff2011-05-03 15:33:24 -0700250 # Setup the profiler.
251 rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [
Jeff Brown87866d92011-02-02 13:27:17 -0800252 '--reset',
Narayan Kamatha2e215a2011-08-16 17:53:51 +0100253 '--kernel-range=' + vma_start + ',' + vma_end] + opcontrol_args + [
Jeff Brown87866d92011-02-02 13:27:17 -0800254 '--setup',
255 '--status', '--verbose-log=all'])
Jeff Brown220b7ff2011-05-03 15:33:24 -0700256 if rc != 0:
257 print '* Failed to setup profiler.'
258 return 1
259 return 0
Jeff Brown87866d92011-02-02 13:27:17 -0800260
Jeff Brown220b7ff2011-05-03 15:33:24 -0700261 def do_shutdown(self, command_args):
262 if len(command_args) != 0:
263 print '* Unsupported shutdown command arguments: %s' % (' '.join(command_args))
264 return 2
265
266 rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [
Jeff Brown87866d92011-02-02 13:27:17 -0800267 '--shutdown'])
268 if rc != 0:
Jeff Brown220b7ff2011-05-03 15:33:24 -0700269 print '* Failed to shutdown.'
270 return 1
271 return 0
Jeff Brown87866d92011-02-02 13:27:17 -0800272
Jeff Brown220b7ff2011-05-03 15:33:24 -0700273 def do_start(self, command_args):
274 if len(command_args) != 0:
275 print '* Unsupported start command arguments: %s' % (' '.join(command_args))
276 return 2
277
278 rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [
Jeff Brown87866d92011-02-02 13:27:17 -0800279 '--start', '--status'])
Jeff Brown220b7ff2011-05-03 15:33:24 -0700280 if rc != 0:
281 print '* Failed to start profiler.'
282 return 1
283 return 0
Jeff Brown87866d92011-02-02 13:27:17 -0800284
Jeff Brown220b7ff2011-05-03 15:33:24 -0700285 def do_stop(self, command_args):
286 if len(command_args) != 0:
287 print '* Unsupported stop command arguments: %s' % (' '.join(command_args))
288 return 2
289
290 rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [
Jeff Brown87866d92011-02-02 13:27:17 -0800291 '--stop', '--status'])
Jeff Brown220b7ff2011-05-03 15:33:24 -0700292 if rc != 0:
293 print '* Failed to stop profiler.'
294 return 1
295 return 0
Jeff Brown87866d92011-02-02 13:27:17 -0800296
Jeff Brown220b7ff2011-05-03 15:33:24 -0700297 def do_status(self, command_args):
298 if len(command_args) != 0:
299 print '* Unsupported status command arguments: %s' % (' '.join(command_args))
300 return 2
301
302 rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [
Jeff Brown87866d92011-02-02 13:27:17 -0800303 '--status'])
Jeff Brown220b7ff2011-05-03 15:33:24 -0700304 if rc != 0:
305 print '* Failed to get profiler status.'
306 return 1
307 return 0
Jeff Brown87866d92011-02-02 13:27:17 -0800308
Jeff Brown220b7ff2011-05-03 15:33:24 -0700309 def do_import(self, command_args):
310 force = False
311
312 try:
313 opts, args = getopt.getopt(command_args,
314 'f', ['force'])
315 except getopt.GetoptError, e:
316 print '* Unsupported import command arguments:', str(e)
317 return 2
318
319 for o, a in opts:
320 if o in ('-f', '--force'):
321 force = True
322
323 if len(args) != 0:
324 print '* Unsupported import command arguments: %s' % (' '.join(args))
325 return 2
326
327 # Create session directory.
328 print 'Creating session directory.'
329 if os.path.exists(self.session_dir):
330 if not force:
331 print "* Session directory already exists: %s" % (self.session_dir)
332 print "* Use --force to remove and recreate the session directory."
333 return 1
334
335 try:
336 shutil.rmtree(self.session_dir)
337 except e:
338 print "* Failed to remove existing session directory: %s" % (self.session_dir)
339 print e
340 return 1
341
342 try:
343 os.makedirs(self.session_dir)
344 except e:
345 print "* Failed to create session directory: %s" % (self.session_dir)
346 print e
347 return 1
348
349 raw_samples_dir = os.path.join(self.session_dir, 'raw_samples')
350 samples_dir = os.path.join(self.session_dir, 'samples')
351 abi_file = os.path.join(self.session_dir, 'abi')
352
353 # Dump samples.
354 print 'Dumping samples.'
355 rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [
356 '--dump', '--status'])
357 if rc != 0:
358 print '* Failed to dump samples.'
359 print output
360 return 1
361
362 # Pull samples.
363 print 'Pulling samples from device.'
364 rc, output = self.adb.pull('/data/oprofile/samples/', raw_samples_dir + '/', echo=False)
365 if rc != 0:
366 print '* Failed to pull samples from the device.'
367 print output
368 return 1
369
370 # Pull ABI.
371 print 'Pulling ABI information from device.'
372 rc, output = self.adb.pull('/data/oprofile/abi', abi_file, echo=False)
373 if rc != 0:
374 print '* Failed to pull abi information from the device.'
375 print output
376 return 1
377
378 # Invoke opimport on each sample file to convert it from the device ABI (ARM)
379 # to the host ABI (x86).
380 print 'Importing samples.'
381 for dirpath, dirnames, filenames in os.walk(raw_samples_dir):
382 for filename in filenames:
383 if not re.match('^.*\.log$', filename):
384 in_path = os.path.join(dirpath, filename)
385 out_path = os.path.join(samples_dir, os.path.relpath(in_path, raw_samples_dir))
386 out_dir = os.path.dirname(out_path)
387 try:
388 os.makedirs(out_dir)
389 except e:
390 print "* Failed to create sample directory: %s" % (out_dir)
391 print e
392 return 1
393
394 rc, output = execute([opimport_bin, '-a', abi_file, '-o', out_path, in_path], echo=False)
395 if rc != 0:
396 print '* Failed to import samples.'
397 print output
398 return 1
399
400 # Generate a short summary report.
401 rc, output = self._execute_opreport([])
402 if rc != 0:
403 print '* Failed to generate summary report.'
404 return 1
405 return 0
406
407 def do_report(self, command_args):
408 rc, output = self._execute_opreport(command_args)
409 if rc != 0:
410 print '* Failed to generate report.'
411 return 1
412 return 0
413
414 def _opcontrol_verbose_arg(self):
Jeff Brown87866d92011-02-02 13:27:17 -0800415 if self.verbose:
416 return ['--verbose']
417 else:
418 return []
419
Jeff Brown220b7ff2011-05-03 15:33:24 -0700420 def _execute_opreport(self, args):
421 return execute([opreport_bin,
422 '--session-dir=' + self.session_dir,
423 '--image-path=' + symbols_dir + ',' + system_dir] + args)
424
Jeff Brown87866d92011-02-02 13:27:17 -0800425# Main entry point
426tool = Tool(sys.argv)
427rc = tool.main()
Jeff Brownc3efb232011-03-17 16:08:14 -0700428sys.exit(rc)