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 != 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 = 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
240def 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
261html_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">
269document.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
300html_suffix = """\\n";
301  </script>
302<!-- END TRACE -->
303</body>
304</html>
305"""
306
307compiled_css_tag = """<style type="text/css">%s</style>"""
308compiled_js_tag = """<script language="javascript">%s</script>"""
309
310linked_css_tag = """<link rel="stylesheet" href="%s"></link>"""
311linked_js_tag = """<script language="javascript" src="%s"></script>"""
312
313if __name__ == '__main__':
314  main()
315