blob: 4e0509048dff6d0b92af3be2939796efb7a42d0f [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)"
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"
Jeff Brown87866d92011-02-02 13:27:17 -0800135 print
136
137 def main(self):
Jeff Brown220b7ff2011-05-03 15:33:24 -0700138 rc = self.do_main()
139 if rc == 2:
140 print
141 self.usage()
142 return rc
143
144 def do_main(self):
Jeff Brown87866d92011-02-02 13:27:17 -0800145 try:
146 opts, args = getopt.getopt(self.argv[1:],
Jeff Brown220b7ff2011-05-03 15:33:24 -0700147 'hs:vd', ['help', 'serial=', 'dir=', 'verbose'])
Jeff Brown87866d92011-02-02 13:27:17 -0800148 except getopt.GetoptError, e:
Jeff Brown87866d92011-02-02 13:27:17 -0800149 print str(e)
Jeff Brown220b7ff2011-05-03 15:33:24 -0700150 return 2
Jeff Brown87866d92011-02-02 13:27:17 -0800151
152 serial_number = None
Jeff Brown87866d92011-02-02 13:27:17 -0800153 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
Jeff Brown220b7ff2011-05-03 15:33:24 -0700159 elif o in ('-d', '--dir'):
160 self.session_dir = a
Jeff Brown87866d92011-02-02 13:27:17 -0800161 elif o in ('-v', '--verbose'):
162 self.verbose = True
Jeff Brown220b7ff2011-05-03 15:33:24 -0700163
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:]
Jeff Brown87866d92011-02-02 13:27:17 -0800170
171 self.adb = Adb(serial_number)
172
173 if command == 'setup':
Jeff Brown220b7ff2011-05-03 15:33:24 -0700174 rc = self.do_setup(command_args)
Jeff Brown87866d92011-02-02 13:27:17 -0800175 elif command == 'shutdown':
Jeff Brown220b7ff2011-05-03 15:33:24 -0700176 rc = self.do_shutdown(command_args)
Jeff Brown87866d92011-02-02 13:27:17 -0800177 elif command == 'start':
Jeff Brown220b7ff2011-05-03 15:33:24 -0700178 rc = self.do_start(command_args)
Jeff Brown87866d92011-02-02 13:27:17 -0800179 elif command == 'stop':
Jeff Brown220b7ff2011-05-03 15:33:24 -0700180 rc = self.do_stop(command_args)
Jeff Brown87866d92011-02-02 13:27:17 -0800181 elif command == 'status':
Jeff Brown220b7ff2011-05-03 15:33:24 -0700182 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)
Jeff Brown87866d92011-02-02 13:27:17 -0800187 else:
Jeff Brown220b7ff2011-05-03 15:33:24 -0700188 print '* Unknown command: ' + command
189 return 2
190
Jeff Brown87866d92011-02-02 13:27:17 -0800191 return rc
192
Jeff Brown220b7ff2011-05-03 15:33:24 -0700193 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.
Jeff Brown87866d92011-02-02 13:27:17 -0800236 rc, output = self.adb.shell(['cat', '/proc/kallsyms'], echo=False)
237 if rc != 0:
Jeff Brown220b7ff2011-05-03 15:33:24 -0700238 print '* Failed to determine kernel VMA range.'
239 print output
240 return 1
Jeff Brown87866d92011-02-02 13:27:17 -0800241 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
Jeff Brown220b7ff2011-05-03 15:33:24 -0700244 # Setup the profiler.
245 rc, output = self.adb.shell(['/system/xbin/opcontrol'] + self._opcontrol_verbose_arg() + [
Jeff Brown87866d92011-02-02 13:27:17 -0800246 '--reset',
Jeff Brown220b7ff2011-05-03 15:33:24 -0700247 '--kernel-range=' + vma_start + '-' + vma_end] + opcontrol_args + [
Jeff Brown87866d92011-02-02 13:27:17 -0800248 '--setup',
249 '--status', '--verbose-log=all'])
Jeff Brown220b7ff2011-05-03 15:33:24 -0700250 if rc != 0:
251 print '* Failed to setup profiler.'
252 return 1
253 return 0
Jeff Brown87866d92011-02-02 13:27:17 -0800254
Jeff Brown220b7ff2011-05-03 15:33:24 -0700255 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() + [
Jeff Brown87866d92011-02-02 13:27:17 -0800261 '--shutdown'])
262 if rc != 0:
Jeff Brown220b7ff2011-05-03 15:33:24 -0700263 print '* Failed to shutdown.'
264 return 1
265 return 0
Jeff Brown87866d92011-02-02 13:27:17 -0800266
Jeff Brown220b7ff2011-05-03 15:33:24 -0700267 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() + [
Jeff Brown87866d92011-02-02 13:27:17 -0800273 '--start', '--status'])
Jeff Brown220b7ff2011-05-03 15:33:24 -0700274 if rc != 0:
275 print '* Failed to start profiler.'
276 return 1
277 return 0
Jeff Brown87866d92011-02-02 13:27:17 -0800278
Jeff Brown220b7ff2011-05-03 15:33:24 -0700279 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() + [
Jeff Brown87866d92011-02-02 13:27:17 -0800285 '--stop', '--status'])
Jeff Brown220b7ff2011-05-03 15:33:24 -0700286 if rc != 0:
287 print '* Failed to stop profiler.'
288 return 1
289 return 0
Jeff Brown87866d92011-02-02 13:27:17 -0800290
Jeff Brown220b7ff2011-05-03 15:33:24 -0700291 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() + [
Jeff Brown87866d92011-02-02 13:27:17 -0800297 '--status'])
Jeff Brown220b7ff2011-05-03 15:33:24 -0700298 if rc != 0:
299 print '* Failed to get profiler status.'
300 return 1
301 return 0
Jeff Brown87866d92011-02-02 13:27:17 -0800302
Jeff Brown220b7ff2011-05-03 15:33:24 -0700303 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):
Jeff Brown87866d92011-02-02 13:27:17 -0800409 if self.verbose:
410 return ['--verbose']
411 else:
412 return []
413
Jeff Brown220b7ff2011-05-03 15:33:24 -0700414 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
Jeff Brown87866d92011-02-02 13:27:17 -0800419# Main entry point
420tool = Tool(sys.argv)
421rc = tool.main()
Jeff Brownc3efb232011-03-17 16:08:14 -0700422sys.exit(rc)