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