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_html_file = 'systrace_trace_viewer.html'
30
31def add_adb_serial(command, serial):
32  if serial != None:
33    command.insert(1, serial)
34    command.insert(1, '-s')
35
36def 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
204def read_asset(src_dir, filename):
205  return open(os.path.join(src_dir, filename)).read()
206
207if __name__ == '__main__':
208  main()
209