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 is not 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, templates = 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 
    139   else:
    140     css_filename = os.path.join(script_dir, flattened_css_file)
    141     js_filename = os.path.join(script_dir, flattened_js_file)
    142     css = compiled_css_tag % (open(css_filename).read())
    143     js = compiled_js_tag % (open(js_filename).read())
    144     templates = ''
    145 
    146   html_filename = options.output_file
    147 
    148   adb = subprocess.Popen(atrace_args, stdout=subprocess.PIPE,
    149                          stderr=subprocess.PIPE)
    150 
    151   result = None
    152   data = []
    153 
    154   # Read the text portion of the output and watch for the 'TRACE:' marker that
    155   # indicates the start of the trace data.
    156   while result is None:
    157     ready = select.select([adb.stdout, adb.stderr], [], [adb.stdout, adb.stderr])
    158     if adb.stderr in ready[0]:
    159       err = os.read(adb.stderr.fileno(), 4096)
    160       sys.stderr.write(err)
    161       sys.stderr.flush()
    162     if adb.stdout in ready[0]:
    163       out = os.read(adb.stdout.fileno(), 4096)
    164       parts = out.split('\nTRACE:', 1)
    165 
    166       txt = parts[0].replace('\r', '')
    167       if len(parts) == 2:
    168         # The '\nTRACE:' match stole the last newline from the text, so add it
    169         # back here.
    170         txt += '\n'
    171       sys.stdout.write(txt)
    172       sys.stdout.flush()
    173 
    174       if len(parts) == 2:
    175         data.append(parts[1])
    176         sys.stdout.write("downloading trace...")
    177         sys.stdout.flush()
    178         break
    179 
    180     result = adb.poll()
    181 
    182   # Read and buffer the data portion of the output.
    183   while True:
    184     ready = select.select([adb.stdout, adb.stderr], [], [adb.stdout, adb.stderr])
    185     keepReading = False
    186     if adb.stderr in ready[0]:
    187       err = os.read(adb.stderr.fileno(), 4096)
    188       if len(err) > 0:
    189         keepReading = True
    190         sys.stderr.write(err)
    191         sys.stderr.flush()
    192     if adb.stdout in ready[0]:
    193       out = os.read(adb.stdout.fileno(), 4096)
    194       if len(out) > 0:
    195         keepReading = True
    196         data.append(out)
    197 
    198     if result is not None and not keepReading:
    199       break
    200 
    201     result = adb.poll()
    202 
    203   if result == 0:
    204     if expect_trace:
    205       data = ''.join(data)
    206 
    207       # Collapse CRLFs that are added by adb shell.
    208       if data.startswith('\r\n'):
    209         data = data.replace('\r\n', '\n')
    210 
    211       # Skip the initial newline.
    212       data = data[1:]
    213 
    214       if not data:
    215         print >> sys.stderr, ('No data was captured.  Output file was not ' +
    216           'written.')
    217         sys.exit(1)
    218       else:
    219         # Indicate to the user that the data download is complete.
    220         print " done\n"
    221 
    222       html_prefix = read_asset(script_dir, 'prefix.html')
    223       html_suffix = read_asset(script_dir, 'suffix.html')
    224 
    225       html_file = open(html_filename, 'w')
    226       html_file.write(html_prefix % (css, js, templates))
    227 
    228       size = 4096
    229       dec = zlib.decompressobj()
    230       for chunk in (data[i:i+size] for i in xrange(0, len(data), size)):
    231         decoded_chunk = dec.decompress(chunk)
    232         html_chunk = decoded_chunk.replace('\n', '\\n\\\n')
    233         html_file.write(html_chunk)
    234 
    235       html_out = dec.flush().replace('\n', '\\n\\\n')
    236       html_file.write(html_out)
    237       html_file.write(html_suffix)
    238       html_file.close()
    239       print "\n    wrote file://%s\n" % os.path.abspath(options.output_file)
    240 
    241   else: # i.e. result != 0
    242     print >> sys.stderr, 'adb returned error code %d' % result
    243     sys.exit(1)
    244 
    245 def read_asset(src_dir, filename):
    246   return open(os.path.join(src_dir, filename)).read()
    247 
    248 def get_assets(src_dir, build_dir):
    249   sys.path.append(build_dir)
    250   gen = __import__('generate_standalone_timeline_view', {}, {})
    251   parse_deps = __import__('parse_deps', {}, {})
    252   gen_templates = __import__('generate_template_contents', {}, {})
    253   filenames = gen._get_input_filenames()
    254   load_sequence = parse_deps.calc_load_sequence(filenames, src_dir)
    255 
    256   js_files = []
    257   js_flattenizer = "window.FLATTENED = {};\n"
    258   js_flattenizer += "window.FLATTENED_RAW_SCRIPTS = {};\n"
    259   css_files = []
    260 
    261   for module in load_sequence:
    262     js_files.append(os.path.relpath(module.filename, src_dir))
    263     js_flattenizer += "window.FLATTENED['%s'] = true;\n" % module.name
    264     for dependent_raw_script_name in module.dependent_raw_script_names:
    265       js_flattenizer += (
    266         "window.FLATTENED_RAW_SCRIPTS['%s'] = true;\n" %
    267         dependent_raw_script_name)
    268 
    269     for style_sheet in module.style_sheets:
    270       css_files.append(os.path.relpath(style_sheet.filename, src_dir))
    271 
    272   templates = gen_templates.generate_templates()
    273 
    274   sys.path.pop()
    275 
    276   return (js_files, js_flattenizer, css_files, templates)
    277 
    278 
    279 compiled_css_tag = """<style type="text/css">%s</style>"""
    280 compiled_js_tag = """<script language="javascript">%s</script>"""
    281 
    282 linked_css_tag = """<link rel="stylesheet" href="%s"></link>"""
    283 linked_js_tag = """<script language="javascript" src="%s"></script>"""
    284 
    285 if __name__ == '__main__':
    286   main()
    287