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 flattened_css_file = 'style.css'
     16 flattened_js_file = 'script.js'
     17 
     18 class OptionParserIgnoreErrors(optparse.OptionParser):
     19   def error(self, msg):
     20     pass
     21 
     22   def exit(self):
     23     pass
     24 
     25   def print_usage(self):
     26     pass
     27 
     28   def print_help(self):
     29     pass
     30 
     31   def print_version(self):
     32     pass
     33 
     34 def get_device_sdk_version():
     35   getprop_args = ['adb', 'shell', 'getprop', 'ro.build.version.sdk']
     36 
     37   parser = OptionParserIgnoreErrors()
     38   parser.add_option('-e', '--serial', dest='device_serial', type='string')
     39   options, args = parser.parse_args()
     40   if options.device_serial is not None:
     41     getprop_args[1:1] = ['-s', options.device_serial]
     42 
     43   adb = subprocess.Popen(getprop_args, stdout=subprocess.PIPE,
     44                          stderr=subprocess.PIPE)
     45   out, err = adb.communicate()
     46   if adb.returncode != 0:
     47     print >> sys.stderr, 'Error querying device SDK-version:'
     48     print >> sys.stderr, err
     49     sys.exit(1)
     50 
     51   version = int(out)
     52   return version
     53 
     54 def add_adb_serial(command, serial):
     55   if serial != None:
     56     command.insert(1, serial)
     57     command.insert(1, '-s')
     58 
     59 def main():
     60   device_sdk_version = get_device_sdk_version()
     61   if device_sdk_version < 18:
     62     legacy_script = os.path.join(os.path.dirname(sys.argv[0]), 'systrace-legacy.py')
     63     os.execv(legacy_script, sys.argv)
     64 
     65   usage = "Usage: %prog [options] [category1 [category2 ...]]"
     66   desc = "Example: %prog -b 32768 -t 15 gfx input view sched freq"
     67   parser = optparse.OptionParser(usage=usage, description=desc)
     68   parser.add_option('-o', dest='output_file', help='write HTML to FILE',
     69                     default='trace.html', metavar='FILE')
     70   parser.add_option('-t', '--time', dest='trace_time', type='int',
     71                     help='trace for N seconds', metavar='N')
     72   parser.add_option('-b', '--buf-size', dest='trace_buf_size', type='int',
     73                     help='use a trace buffer size of N KB', metavar='N')
     74   parser.add_option('-k', '--ktrace', dest='kfuncs', action='store',
     75                     help='specify a comma-separated list of kernel functions to trace')
     76   parser.add_option('-l', '--list-categories', dest='list_categories', default=False,
     77                     action='store_true', help='list the available categories and exit')
     78   parser.add_option('-a', '--app', dest='app_name', default=None, type='string',
     79                     action='store', help='enable application-level tracing for comma-separated ' +
     80                     'list of app cmdlines')
     81 
     82   parser.add_option('--link-assets', dest='link_assets', default=False,
     83                     action='store_true', help='link to original CSS or JS resources '
     84                     'instead of embedding them')
     85   parser.add_option('--from-file', dest='from_file', action='store',
     86                     help='read the trace from a file (compressed) rather than running a live trace')
     87   parser.add_option('--asset-dir', dest='asset_dir', default='trace-viewer',
     88                     type='string', help='')
     89   parser.add_option('-e', '--serial', dest='device_serial', type='string',
     90                     help='adb device serial number')
     91 
     92   options, args = parser.parse_args()
     93 
     94   if options.list_categories:
     95     atrace_args = ['adb', 'shell', 'atrace', '--list_categories']
     96     expect_trace = False
     97   elif options.from_file is not None:
     98     atrace_args = ['cat', options.from_file]
     99     expect_trace = True
    100   else:
    101     atrace_args = ['adb', 'shell', 'atrace', '-z']
    102     expect_trace = True
    103 
    104     if options.trace_time is not None:
    105       if options.trace_time > 0:
    106         atrace_args.extend(['-t', str(options.trace_time)])
    107       else:
    108         parser.error('the trace time must be a positive number')
    109 
    110     if options.trace_buf_size is not None:
    111       if options.trace_buf_size > 0:
    112         atrace_args.extend(['-b', str(options.trace_buf_size)])
    113       else:
    114         parser.error('the trace buffer size must be a positive number')
    115 
    116     if options.app_name is not None:
    117       atrace_args.extend(['-a', options.app_name])
    118 
    119     if options.kfuncs is not None:
    120       atrace_args.extend(['-k', options.kfuncs])
    121 
    122     atrace_args.extend(args)
    123 
    124   if atrace_args[0] == 'adb':
    125     add_adb_serial(atrace_args, options.device_serial)
    126 
    127   script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
    128 
    129   if options.link_assets:
    130     src_dir = os.path.join(script_dir, options.asset_dir, 'src')
    131     build_dir = os.path.join(script_dir, options.asset_dir, 'build')
    132 
    133     js_files, js_flattenizer, css_files = get_assets(src_dir, build_dir)
    134 
    135     css = '\n'.join(linked_css_tag % (os.path.join(src_dir, f)) for f in css_files)
    136     js = '<script language="javascript">\n%s</script>\n' % js_flattenizer
    137     js += '\n'.join(linked_js_tag % (os.path.join(src_dir, f)) for f in js_files)
    138   else:
    139     css_filename = os.path.join(script_dir, flattened_css_file)
    140     js_filename = os.path.join(script_dir, flattened_js_file)
    141     css = compiled_css_tag % (open(css_filename).read())
    142     js = compiled_js_tag % (open(js_filename).read())
    143 
    144   html_filename = options.output_file
    145 
    146   adb = subprocess.Popen(atrace_args, stdout=subprocess.PIPE,
    147                          stderr=subprocess.PIPE)
    148 
    149   result = None
    150   data = []
    151 
    152   # Read the text portion of the output and watch for the 'TRACE:' marker that
    153   # indicates the start of the trace data.
    154   while result is None:
    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 = os.read(adb.stdout.fileno(), 4096)
    162       parts = out.split('\nTRACE:', 1)
    163 
    164       txt = parts[0].replace('\r', '')
    165       if len(parts) == 2:
    166         # The '\nTRACE:' match stole the last newline from the text, so add it
    167         # back here.
    168         txt += '\n'
    169       sys.stdout.write(txt)
    170       sys.stdout.flush()
    171 
    172       if len(parts) == 2:
    173         data.append(parts[1])
    174         sys.stdout.write("downloading trace...")
    175         sys.stdout.flush()
    176         break
    177 
    178     result = adb.poll()
    179 
    180   # Read and buffer the data portion of the output.
    181   while True:
    182     ready = select.select([adb.stdout, adb.stderr], [], [adb.stdout, adb.stderr])
    183     keepReading = False
    184     if adb.stderr in ready[0]:
    185       err = os.read(adb.stderr.fileno(), 4096)
    186       if len(err) > 0:
    187         keepReading = True
    188         sys.stderr.write(err)
    189         sys.stderr.flush()
    190     if adb.stdout in ready[0]:
    191       out = os.read(adb.stdout.fileno(), 4096)
    192       if len(out) > 0:
    193         keepReading = True
    194         data.append(out)
    195 
    196     if result is not None and not keepReading:
    197       break
    198 
    199     result = adb.poll()
    200 
    201   if result == 0:
    202     if expect_trace:
    203       data = ''.join(data)
    204 
    205       # Collapse CRLFs that are added by adb shell.
    206       if data.startswith('\r\n'):
    207         data = data.replace('\r\n', '\n')
    208 
    209       # Skip the initial newline.
    210       data = data[1:]
    211 
    212       if not data:
    213         print >> sys.stderr, ('No data was captured.  Output file was not ' +
    214           'written.')
    215         sys.exit(1)
    216       else:
    217         # Indicate to the user that the data download is complete.
    218         print " done\n"
    219 
    220       html_file = open(html_filename, 'w')
    221       html_file.write(html_prefix % (css, js))
    222 
    223       size = 4096
    224       dec = zlib.decompressobj()
    225       for chunk in (data[i:i+size] for i in xrange(0, len(data), size)):
    226         decoded_chunk = dec.decompress(chunk)
    227         html_chunk = decoded_chunk.replace('\n', '\\n\\\n')
    228         html_file.write(html_chunk)
    229 
    230       html_out = dec.flush().replace('\n', '\\n\\\n')
    231       html_file.write(html_out)
    232       html_file.write(html_suffix)
    233       html_file.close()
    234       print "\n    wrote file://%s/%s\n" % (os.getcwd(), options.output_file)
    235 
    236   else: # i.e. result != 0
    237     print >> sys.stderr, 'adb returned error code %d' % result
    238     sys.exit(1)
    239 
    240 def get_assets(src_dir, build_dir):
    241   sys.path.append(build_dir)
    242   gen = __import__('generate_standalone_timeline_view', {}, {})
    243   parse_deps = __import__('parse_deps', {}, {})
    244   filenames = gen._get_input_filenames()
    245   load_sequence = parse_deps.calc_load_sequence(filenames, src_dir)
    246 
    247   js_files = []
    248   js_flattenizer = "window.FLATTENED = {};\n"
    249   css_files = []
    250 
    251   for module in load_sequence:
    252     js_files.append(os.path.relpath(module.filename, src_dir))
    253     js_flattenizer += "window.FLATTENED['%s'] = true;\n" % module.name
    254     for style_sheet in module.style_sheets:
    255       css_files.append(os.path.relpath(style_sheet.filename, src_dir))
    256 
    257   sys.path.pop()
    258 
    259   return (js_files, js_flattenizer, css_files)
    260 
    261 html_prefix = """<!DOCTYPE HTML>
    262 <html>
    263 <head i18n-values="dir:textdirection;">
    264 <meta charset="utf-8"/>
    265 <title>Android System Trace</title>
    266 %s
    267 %s
    268 <script language="javascript">
    269 document.addEventListener('DOMContentLoaded', function() {
    270   if (!linuxPerfData)
    271     return;
    272 
    273   var m = new tracing.Model(linuxPerfData);
    274   var timelineViewEl = document.querySelector('.view');
    275   tracing.ui.decorate(timelineViewEl, tracing.TimelineView);
    276   timelineViewEl.model = m;
    277   timelineViewEl.tabIndex = 1;
    278   timelineViewEl.timeline.focusElement = timelineViewEl;
    279 });
    280 </script>
    281 <style>
    282   .view {
    283     overflow: hidden;
    284     position: absolute;
    285     top: 0;
    286     bottom: 0;
    287     left: 0;
    288     right: 0;
    289   }
    290 </style>
    291 </head>
    292 <body>
    293   <div class="view">
    294   </div>
    295 <!-- BEGIN TRACE -->
    296   <script>
    297   var linuxPerfData = "\\
    298 """
    299 
    300 html_suffix = """\\n";
    301   </script>
    302 <!-- END TRACE -->
    303 </body>
    304 </html>
    305 """
    306 
    307 compiled_css_tag = """<style type="text/css">%s</style>"""
    308 compiled_js_tag = """<script language="javascript">%s</script>"""
    309 
    310 linked_css_tag = """<link rel="stylesheet" href="%s"></link>"""
    311 linked_js_tag = """<script language="javascript" src="%s"></script>"""
    312 
    313 if __name__ == '__main__':
    314   main()
    315