Home | History | Annotate | Download | only in chromium-trace
      1 #!/usr/bin/env python
      2 
      3 # Copyright (c) 2011 The Chromium Authors. All rights reserved.
      4 # Use of this source code is governed by a BSD-style license that can be
      5 # found in the LICENSE file.
      6 
      7 """Android system-wide tracing utility.
      8 
      9 This is a tool for capturing a trace that includes data from both userland and
     10 the kernel.  It creates an HTML file for visualizing the trace.
     11 """
     12 
     13 import errno, optparse, os, select, subprocess, sys, time, zlib
     14 
     15 # This list is based on the tags in frameworks/native/include/utils/Trace.h.
     16 trace_tag_bits = {
     17   'gfx':      1<<1,
     18   'input':    1<<2,
     19   'view':     1<<3,
     20   'webview':  1<<4,
     21   'wm':       1<<5,
     22   'am':       1<<6,
     23   'sync':     1<<7,
     24   'audio':    1<<8,
     25   'video':    1<<9,
     26   'camera':   1<<10,
     27 }
     28 
     29 flattened_html_file = 'systrace_trace_viewer.html'
     30 
     31 def add_adb_serial(command, serial):
     32   if serial != None:
     33     command.insert(1, serial)
     34     command.insert(1, '-s')
     35 
     36 def main():
     37   parser = optparse.OptionParser()
     38   parser.add_option('-o', dest='output_file', help='write HTML to FILE',
     39                     default='trace.html', metavar='FILE')
     40   parser.add_option('-t', '--time', dest='trace_time', type='int',
     41                     help='trace for N seconds', metavar='N')
     42   parser.add_option('-b', '--buf-size', dest='trace_buf_size', type='int',
     43                     help='use a trace buffer size of N KB', metavar='N')
     44   parser.add_option('-d', '--disk', dest='trace_disk', default=False,
     45                     action='store_true', help='trace disk I/O (requires root)')
     46   parser.add_option('-f', '--cpu-freq', dest='trace_cpu_freq', default=False,
     47                     action='store_true', help='trace CPU frequency changes')
     48   parser.add_option('-i', '--cpu-idle', dest='trace_cpu_idle', default=False,
     49                     action='store_true', help='trace CPU idle events')
     50   parser.add_option('-l', '--cpu-load', dest='trace_cpu_load', default=False,
     51                     action='store_true', help='trace CPU load')
     52   parser.add_option('-s', '--no-cpu-sched', dest='trace_cpu_sched', default=True,
     53                     action='store_false', help='inhibit tracing CPU ' +
     54                     'scheduler (allows longer trace times by reducing data ' +
     55                     'rate into buffer)')
     56   parser.add_option('-u', '--bus-utilization', dest='trace_bus_utilization',
     57                     default=False, action='store_true',
     58                     help='trace bus utilization (requires root)')
     59   parser.add_option('-w', '--workqueue', dest='trace_workqueue', default=False,
     60                     action='store_true', help='trace the kernel workqueues ' +
     61                     '(requires root)')
     62   parser.add_option('--set-tags', dest='set_tags', action='store',
     63                     help='set the enabled trace tags and exit; set to a ' +
     64                     'comma separated list of: ' +
     65                     ', '.join(trace_tag_bits.iterkeys()))
     66   parser.add_option('--link-assets', dest='link_assets', default=False,
     67                     action='store_true', help='link to original CSS or JS resources '
     68                     'instead of embedding them')
     69   parser.add_option('--from-file', dest='from_file', action='store',
     70                     help='read the trace from a file rather than running a live trace')
     71   parser.add_option('--asset-dir', dest='asset_dir', default='trace-viewer',
     72                     type='string', help='')
     73   parser.add_option('-e', '--serial', dest='device_serial', type='string',
     74                     help='adb device serial number')
     75   options, args = parser.parse_args()
     76 
     77   if options.link_assets or options.asset_dir != 'trace-viewer':
     78     parser.error('--link-assets and --asset-dir is deprecated.')
     79 
     80   if options.set_tags:
     81     flags = 0
     82     tags = options.set_tags.split(',')
     83     for tag in tags:
     84       try:
     85         flags |= trace_tag_bits[tag]
     86       except KeyError:
     87         parser.error('unrecognized tag: %s\nknown tags are: %s' %
     88                      (tag, ', '.join(trace_tag_bits.iterkeys())))
     89     atrace_args = ['adb', 'shell', 'setprop', 'debug.atrace.tags.enableflags', hex(flags)]
     90     add_adb_serial(atrace_args, options.device_serial)
     91     try:
     92       subprocess.check_call(atrace_args)
     93     except subprocess.CalledProcessError, e:
     94       print >> sys.stderr, 'unable to set tags: %s' % e
     95     print '\nSet enabled tags to: %s\n' % ', '.join(tags)
     96     print ('You will likely need to restart the Android framework for this to ' +
     97           'take effect:\n\n    adb shell stop\n    adb shell ' +
     98           'start\n')
     99     return
    100 
    101   atrace_args = ['adb', 'shell', 'atrace', '-z']
    102   add_adb_serial(atrace_args, options.device_serial)
    103 
    104   if options.trace_disk:
    105     atrace_args.append('-d')
    106   if options.trace_cpu_freq:
    107     atrace_args.append('-f')
    108   if options.trace_cpu_idle:
    109     atrace_args.append('-i')
    110   if options.trace_cpu_load:
    111     atrace_args.append('-l')
    112   if options.trace_cpu_sched:
    113     atrace_args.append('-s')
    114   if options.trace_bus_utilization:
    115     atrace_args.append('-u')
    116   if options.trace_workqueue:
    117     atrace_args.append('-w')
    118   if options.trace_time is not None:
    119     if options.trace_time > 0:
    120       atrace_args.extend(['-t', str(options.trace_time)])
    121     else:
    122       parser.error('the trace time must be a positive number')
    123   if options.trace_buf_size is not None:
    124     if options.trace_buf_size > 0:
    125       atrace_args.extend(['-b', str(options.trace_buf_size)])
    126     else:
    127       parser.error('the trace buffer size must be a positive number')
    128 
    129   if options.from_file is not None:
    130     atrace_args = ['cat', options.from_file]
    131 
    132   script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
    133 
    134   with open(os.path.join(script_dir, flattened_html_file), 'r') as f:
    135     trace_viewer_html = f.read()
    136 
    137   html_filename = options.output_file
    138 
    139   trace_started = False
    140   leftovers = ''
    141   adb = subprocess.Popen(atrace_args, stdout=subprocess.PIPE,
    142                          stderr=subprocess.PIPE)
    143   dec = zlib.decompressobj()
    144   while True:
    145     ready = select.select([adb.stdout, adb.stderr], [], [adb.stdout, adb.stderr])
    146     if adb.stderr in ready[0]:
    147       err = os.read(adb.stderr.fileno(), 4096)
    148       sys.stderr.write(err)
    149       sys.stderr.flush()
    150     if adb.stdout in ready[0]:
    151       out = leftovers + os.read(adb.stdout.fileno(), 4096)
    152       if options.from_file is None:
    153         out = out.replace('\r\n', '\n')
    154       if out.endswith('\r'):
    155         out = out[:-1]
    156         leftovers = '\r'
    157       else:
    158         leftovers = ''
    159       if not trace_started:
    160         lines = out.splitlines(True)
    161         out = ''
    162         for i, line in enumerate(lines):
    163           if line == 'TRACE:\n':
    164             sys.stdout.write("downloading trace...")
    165             sys.stdout.flush()
    166             out = ''.join(lines[i+1:])
    167             html_prefix = read_asset(script_dir, 'prefix.html')
    168             html_file = open(html_filename, 'w')
    169             html_file.write(
    170               html_prefix.replace("{{SYSTRACE_TRACE_VIEWER_HTML}}",
    171                   trace_viewer_html))
    172             html_file.write('<!-- BEGIN TRACE -->\n' +
    173               '  <script class="trace-data" type="application/text">\n')
    174             trace_started = True
    175             break
    176           elif 'TRACE:'.startswith(line) and i == len(lines) - 1:
    177             leftovers = line + leftovers
    178           else:
    179             sys.stdout.write(line)
    180             sys.stdout.flush()
    181       if len(out) > 0:
    182         out = dec.decompress(out)
    183       html_out = out.replace('\n', '\\n\\\n')
    184       if len(html_out) > 0:
    185         html_file.write(html_out)
    186     result = adb.poll()
    187     if result is not None:
    188       break
    189   if result != 0:
    190     print >> sys.stderr, 'adb returned error code %d' % result
    191   elif trace_started:
    192     html_out = dec.flush().replace('\n', '\\n\\\n').replace('\r', '')
    193     if len(html_out) > 0:
    194       html_file.write(html_out)
    195     html_file.write('  </script>\n<!-- END TRACE -->\n')
    196     html_suffix = read_asset(script_dir, 'suffix.html')
    197     html_file.write(html_suffix)
    198     html_file.close()
    199     print " done\n\n    wrote file://%s\n" % (os.path.abspath(options.output_file))
    200   else:
    201     print >> sys.stderr, ('An error occured while capturing the trace.  Output ' +
    202       'file was not written.')
    203 
    204 def read_asset(src_dir, filename):
    205   return open(os.path.join(src_dir, filename)).read()
    206 
    207 if __name__ == '__main__':
    208   main()
    209