• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
9This is a tool for capturing a trace that includes data from both userland and
10the kernel.  It creates an HTML file for visualizing the trace.
11"""
12
13import errno, optparse, os, select, subprocess, sys, time, zlib
14
15# This list is based on the tags in frameworks/native/include/utils/Trace.h.
16trace_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
29flattened_css_file = 'style.css'
30flattened_js_file = 'script.js'
31
32def add_adb_serial(command, serial):
33  if serial != None:
34    command.insert(1, serial)
35    command.insert(1, '-s')
36
37def 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, templates = 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
142  else:
143    css_filename = os.path.join(script_dir, flattened_css_file)
144    js_filename = os.path.join(script_dir, flattened_js_file)
145    css = compiled_css_tag % (open(css_filename).read())
146    js = compiled_js_tag % (open(js_filename).read())
147    templates = ''
148
149  html_filename = options.output_file
150
151  trace_started = False
152  leftovers = ''
153  adb = subprocess.Popen(atrace_args, stdout=subprocess.PIPE,
154                         stderr=subprocess.PIPE)
155  dec = zlib.decompressobj()
156  while True:
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 = leftovers + os.read(adb.stdout.fileno(), 4096)
164      if options.from_file is None:
165        out = out.replace('\r\n', '\n')
166      if out.endswith('\r'):
167        out = out[:-1]
168        leftovers = '\r'
169      else:
170        leftovers = ''
171      if not trace_started:
172        lines = out.splitlines(True)
173        out = ''
174        for i, line in enumerate(lines):
175          if line == 'TRACE:\n':
176            sys.stdout.write("downloading trace...")
177            sys.stdout.flush()
178            out = ''.join(lines[i+1:])
179            html_prefix = read_asset(script_dir, 'prefix.html')
180            html_file = open(html_filename, 'w')
181            html_file.write(html_prefix % (css, js, templates))
182            trace_started = True
183            break
184          elif 'TRACE:'.startswith(line) and i == len(lines) - 1:
185            leftovers = line + leftovers
186          else:
187            sys.stdout.write(line)
188            sys.stdout.flush()
189      if len(out) > 0:
190        out = dec.decompress(out)
191      html_out = out.replace('\n', '\\n\\\n')
192      if len(html_out) > 0:
193        html_file.write(html_out)
194    result = adb.poll()
195    if result is not None:
196      break
197  if result != 0:
198    print >> sys.stderr, 'adb returned error code %d' % result
199  elif trace_started:
200    html_out = dec.flush().replace('\n', '\\n\\\n').replace('\r', '')
201    if len(html_out) > 0:
202      html_file.write(html_out)
203    html_suffix = read_asset(script_dir, 'suffix.html')
204    html_file.write(html_suffix)
205    html_file.close()
206    print " done\n\n    wrote file://%s\n" % (os.path.abspath(options.output_file))
207  else:
208    print >> sys.stderr, ('An error occured while capturing the trace.  Output ' +
209      'file was not written.')
210
211def read_asset(src_dir, filename):
212  return open(os.path.join(src_dir, filename)).read()
213
214def get_assets(src_dir, build_dir):
215  sys.path.append(build_dir)
216  gen = __import__('generate_standalone_timeline_view', {}, {})
217  parse_deps = __import__('parse_deps', {}, {})
218  gen_templates = __import__('generate_template_contents', {}, {})
219  filenames = gen._get_input_filenames()
220  load_sequence = parse_deps.calc_load_sequence(filenames, src_dir)
221
222  js_files = []
223  js_flattenizer = "window.FLATTENED = {};\n"
224  js_flattenizer += "window.FLATTENED_RAW_SCRIPTS = {};\n"
225  css_files = []
226
227  for module in load_sequence:
228    js_files.append(os.path.relpath(module.filename, src_dir))
229    js_flattenizer += "window.FLATTENED['%s'] = true;\n" % module.name
230    for dependent_raw_script_name in module.dependent_raw_script_names:
231      js_flattenizer += (
232        "window.FLATTENED_RAW_SCRIPTS['%s'] = true;\n" %
233        dependent_raw_script_name)
234
235    for style_sheet in module.style_sheets:
236      css_files.append(os.path.relpath(style_sheet.filename, src_dir))
237
238  templates = gen_templates.generate_templates()
239
240  sys.path.pop()
241
242  return (js_files, js_flattenizer, css_files, templates)
243
244compiled_css_tag = """<style type="text/css">%s</style>"""
245compiled_js_tag = """<script language="javascript">%s</script>"""
246
247linked_css_tag = """<link rel="stylesheet" href="%s"></link>"""
248linked_js_tag = """<script language="javascript" src="%s"></script>"""
249
250if __name__ == '__main__':
251  main()
252