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_css_file = 'style.css'
     30 flattened_js_file = 'script.js'
     31 
     32 def add_adb_serial(command, serial):
     33   if serial != None:
     34     command.insert(1, serial)
     35     command.insert(1, '-s')
     36 
     37 def main():
     38   parser = optparse.OptionParser()
     39   parser.add_option('-o', dest='output_file', help='write HTML to FILE',
     40                     default='trace.html', metavar='FILE')
     41   parser.add_option('-t', '--time', dest='trace_time', type='int',
     42                     help='trace for N seconds', metavar='N')
     43   parser.add_option('-b', '--buf-size', dest='trace_buf_size', type='int',
     44                     help='use a trace buffer size of N KB', metavar='N')
     45   parser.add_option('-d', '--disk', dest='trace_disk', default=False,
     46                     action='store_true', help='trace disk I/O (requires root)')
     47   parser.add_option('-f', '--cpu-freq', dest='trace_cpu_freq', default=False,
     48                     action='store_true', help='trace CPU frequency changes')
     49   parser.add_option('-i', '--cpu-idle', dest='trace_cpu_idle', default=False,
     50                     action='store_true', help='trace CPU idle events')
     51   parser.add_option('-l', '--cpu-load', dest='trace_cpu_load', default=False,
     52                     action='store_true', help='trace CPU load')
     53   parser.add_option('-s', '--no-cpu-sched', dest='trace_cpu_sched', default=True,
     54                     action='store_false', help='inhibit tracing CPU ' +
     55                     'scheduler (allows longer trace times by reducing data ' +
     56                     'rate into buffer)')
     57   parser.add_option('-u', '--bus-utilization', dest='trace_bus_utilization',
     58                     default=False, action='store_true',
     59                     help='trace bus utilization (requires root)')
     60   parser.add_option('-w', '--workqueue', dest='trace_workqueue', default=False,
     61                     action='store_true', help='trace the kernel workqueues ' +
     62                     '(requires root)')
     63   parser.add_option('--set-tags', dest='set_tags', action='store',
     64                     help='set the enabled trace tags and exit; set to a ' +
     65                     'comma separated list of: ' +
     66                     ', '.join(trace_tag_bits.iterkeys()))
     67   parser.add_option('--link-assets', dest='link_assets', default=False,
     68                     action='store_true', help='link to original CSS or JS resources '
     69                     'instead of embedding them')
     70   parser.add_option('--from-file', dest='from_file', action='store',
     71                     help='read the trace from a file rather than running a live trace')
     72   parser.add_option('--asset-dir', dest='asset_dir', default='trace-viewer',
     73                     type='string', help='')
     74   parser.add_option('-e', '--serial', dest='device_serial', type='string',
     75                     help='adb device serial number')
     76   options, args = parser.parse_args()
     77 
     78   if options.set_tags:
     79     flags = 0
     80     tags = options.set_tags.split(',')
     81     for tag in tags:
     82       try:
     83         flags |= trace_tag_bits[tag]
     84       except KeyError:
     85         parser.error('unrecognized tag: %s\nknown tags are: %s' %
     86                      (tag, ', '.join(trace_tag_bits.iterkeys())))
     87     atrace_args = ['adb', 'shell', 'setprop', 'debug.atrace.tags.enableflags', hex(flags)]
     88     add_adb_serial(atrace_args, options.device_serial)
     89     try:
     90       subprocess.check_call(atrace_args)
     91     except subprocess.CalledProcessError, e:
     92       print >> sys.stderr, 'unable to set tags: %s' % e
     93     print '\nSet enabled tags to: %s\n' % ', '.join(tags)
     94     print ('You will likely need to restart the Android framework for this to ' +
     95           'take effect:\n\n    adb shell stop\n    adb shell ' +
     96           'start\n')
     97     return
     98 
     99   atrace_args = ['adb', 'shell', 'atrace', '-z']
    100   add_adb_serial(atrace_args, options.device_serial)
    101 
    102   if options.trace_disk:
    103     atrace_args.append('-d')
    104   if options.trace_cpu_freq:
    105     atrace_args.append('-f')
    106   if options.trace_cpu_idle:
    107     atrace_args.append('-i')
    108   if options.trace_cpu_load:
    109     atrace_args.append('-l')
    110   if options.trace_cpu_sched:
    111     atrace_args.append('-s')
    112   if options.trace_bus_utilization:
    113     atrace_args.append('-u')
    114   if options.trace_workqueue:
    115     atrace_args.append('-w')
    116   if options.trace_time is not None:
    117     if options.trace_time > 0:
    118       atrace_args.extend(['-t', str(options.trace_time)])
    119     else:
    120       parser.error('the trace time must be a positive number')
    121   if options.trace_buf_size is not None:
    122     if options.trace_buf_size > 0:
    123       atrace_args.extend(['-b', str(options.trace_buf_size)])
    124     else:
    125       parser.error('the trace buffer size must be a positive number')
    126 
    127   if options.from_file is not None:
    128     atrace_args = ['cat', options.from_file]
    129 
    130   script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
    131 
    132   if options.link_assets:
    133     src_dir = os.path.join(script_dir, options.asset_dir, 'src')
    134     build_dir = os.path.join(script_dir, options.asset_dir, 'build')
    135 
    136     js_files, js_flattenizer, css_files = get_assets(src_dir, build_dir)
    137 
    138     css = '\n'.join(linked_css_tag % (os.path.join(src_dir, f)) for f in css_files)
    139     js = '<script language="javascript">\n%s</script>\n' % js_flattenizer
    140     js += '\n'.join(linked_js_tag % (os.path.join(src_dir, f)) for f in js_files)
    141   else:
    142     css_filename = os.path.join(script_dir, flattened_css_file)
    143     js_filename = os.path.join(script_dir, flattened_js_file)
    144     css = compiled_css_tag % (open(css_filename).read())
    145     js = compiled_js_tag % (open(js_filename).read())
    146 
    147   html_filename = options.output_file
    148 
    149   trace_started = False
    150   leftovers = ''
    151   adb = subprocess.Popen(atrace_args, stdout=subprocess.PIPE,
    152                          stderr=subprocess.PIPE)
    153   dec = zlib.decompressobj()
    154   while True:
    155     ready = select.select([adb.stdout, adb.stderr], [], [adb.stdout, adb.stderr])
    156     if adb.stderr in ready[0]:
    157       err = os.read(adb.stderr.fileno(), 4096)
    158       sys.stderr.write(err)
    159       sys.stderr.flush()
    160     if adb.stdout in ready[0]:
    161       out = leftovers + os.read(adb.stdout.fileno(), 4096)
    162       if options.from_file is None:
    163         out = out.replace('\r\n', '\n')
    164       if out.endswith('\r'):
    165         out = out[:-1]
    166         leftovers = '\r'
    167       else:
    168         leftovers = ''
    169       if not trace_started:
    170         lines = out.splitlines(True)
    171         out = ''
    172         for i, line in enumerate(lines):
    173           if line == 'TRACE:\n':
    174             sys.stdout.write("downloading trace...")
    175             sys.stdout.flush()
    176             out = ''.join(lines[i+1:])
    177             html_file = open(html_filename, 'w')
    178             html_file.write(html_prefix % (css, js))
    179             trace_started = True
    180             break
    181           elif 'TRACE:'.startswith(line) and i == len(lines) - 1:
    182             leftovers = line + leftovers
    183           else:
    184             sys.stdout.write(line)
    185             sys.stdout.flush()
    186       if len(out) > 0:
    187         out = dec.decompress(out)
    188       html_out = out.replace('\n', '\\n\\\n')
    189       if len(html_out) > 0:
    190         html_file.write(html_out)
    191     result = adb.poll()
    192     if result is not None:
    193       break
    194   if result != 0:
    195     print >> sys.stderr, 'adb returned error code %d' % result
    196   elif trace_started:
    197     html_out = dec.flush().replace('\n', '\\n\\\n').replace('\r', '')
    198     if len(html_out) > 0:
    199       html_file.write(html_out)
    200     html_file.write(html_suffix)
    201     html_file.close()
    202     print " done\n\n    wrote file://%s/%s\n" % (os.getcwd(), options.output_file)
    203   else:
    204     print >> sys.stderr, ('An error occured while capturing the trace.  Output ' +
    205       'file was not written.')
    206 
    207 def get_assets(src_dir, build_dir):
    208   sys.path.append(build_dir)
    209   gen = __import__('generate_standalone_timeline_view', {}, {})
    210   parse_deps = __import__('parse_deps', {}, {})
    211   filenames = gen._get_input_filenames()
    212   load_sequence = parse_deps.calc_load_sequence(filenames)
    213 
    214   js_files = []
    215   js_flattenizer = "window.FLATTENED = {};\n"
    216   css_files = []
    217 
    218   for module in load_sequence:
    219     js_files.append(os.path.relpath(module.filename, src_dir))
    220     js_flattenizer += "window.FLATTENED['%s'] = true;\n" % module.name
    221     for style_sheet in module.style_sheets:
    222       css_files.append(os.path.relpath(style_sheet.filename, src_dir))
    223 
    224   sys.path.pop()
    225 
    226   return (js_files, js_flattenizer, css_files)
    227 
    228 html_prefix = """<!DOCTYPE HTML>
    229 <html>
    230 <head i18n-values="dir:textdirection;">
    231 <title>Android System Trace</title>
    232 %s
    233 %s
    234 <script language="javascript">
    235 document.addEventListener('DOMContentLoaded', function() {
    236   if (!linuxPerfData)
    237     return;
    238 
    239   var m = new tracing.Model(linuxPerfData);
    240   var timelineViewEl = document.querySelector('.view');
    241   tracing.ui.decorate(timelineViewEl, tracing.TimelineView);
    242   timelineViewEl.model = m;
    243   timelineViewEl.tabIndex = 1;
    244   timelineViewEl.timeline.focusElement = timelineViewEl;
    245 });
    246 </script>
    247 <style>
    248   .view {
    249     overflow: hidden;
    250     position: absolute;
    251     top: 0;
    252     bottom: 0;
    253     left: 0;
    254     right: 0;
    255   }
    256 </style>
    257 </head>
    258 <body>
    259   <div class="view">
    260   </div>
    261 <!-- BEGIN TRACE -->
    262   <script>
    263   var linuxPerfData = "\\
    264 """
    265 
    266 html_suffix = """\\n";
    267   </script>
    268 <!-- END TRACE -->
    269 </body>
    270 </html>
    271 """
    272 
    273 compiled_css_tag = """<style type="text/css">%s</style>"""
    274 compiled_js_tag = """<script language="javascript">%s</script>"""
    275 
    276 linked_css_tag = """<link rel="stylesheet" href="%s"></link>"""
    277 linked_js_tag = """<script language="javascript" src="%s"></script>"""
    278 
    279 if __name__ == '__main__':
    280   main()
    281