• 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
15flattened_css_file = 'style.css'
16flattened_js_file = 'script.js'
17
18class 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
34def 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
54def add_adb_serial(command, serial):
55  if serial is not None:
56    command.insert(1, serial)
57    command.insert(1, '-s')
58
59def 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
245def read_asset(src_dir, filename):
246  return open(os.path.join(src_dir, filename)).read()
247
248def 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
279compiled_css_tag = """<style type="text/css">%s</style>"""
280compiled_js_tag = """<script language="javascript">%s</script>"""
281
282linked_css_tag = """<link rel="stylesheet" href="%s"></link>"""
283linked_js_tag = """<script language="javascript" src="%s"></script>"""
284
285if __name__ == '__main__':
286  main()
287