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 sys
14
15# Make sure we're using a new enough version of Python.
16# The flags= parameter of re.sub() is new in Python 2.7.
17if sys.version_info[:2] < (2, 7):
18  print >> sys.stderr, '\nThis script requires Python 2.7 or newer.'
19  sys.exit(1)
20
21# pylint: disable=g-bad-import-order,g-import-not-at-top
22import imp
23import optparse
24import os
25
26import util
27
28
29# The default agent directory.
30DEFAULT_AGENT_DIR = 'agents'
31
32
33def parse_options(argv):
34  """Parses and checks the command-line options.
35
36  Returns:
37    A tuple containing the options structure and a list of categories to
38    be traced.
39  """
40  usage = 'Usage: %prog [options] [category1 [category2 ...]]'
41  desc = 'Example: %prog -b 32768 -t 15 gfx input view sched freq'
42  parser = optparse.OptionParser(usage=usage, description=desc)
43  parser.add_option('-o', dest='output_file', help='write HTML to FILE',
44                    default='trace.html', metavar='FILE')
45  parser.add_option('-t', '--time', dest='trace_time', type='int',
46                    help='trace for N seconds', metavar='N')
47  parser.add_option('-b', '--buf-size', dest='trace_buf_size', type='int',
48                    help='use a trace buffer size of N KB', metavar='N')
49  parser.add_option('-k', '--ktrace', dest='kfuncs', action='store',
50                    help='specify a comma-separated list of kernel functions '
51                    'to trace')
52  parser.add_option('-l', '--list-categories', dest='list_categories',
53                    default=False, action='store_true',
54                    help='list the available categories and exit')
55  parser.add_option('-a', '--app', dest='app_name', default=None, type='string',
56                    action='store',
57                    help='enable application-level tracing for comma-separated '
58                    'list of app cmdlines')
59  parser.add_option('--no-fix-threads', dest='fix_threads', default=True,
60                    action='store_false',
61                    help='don\'t fix missing or truncated thread names')
62  parser.add_option('--no-fix-circular', dest='fix_circular', default=True,
63                    action='store_false',
64                    help='don\'t fix truncated circular traces')
65  parser.add_option('--no-compress', dest='compress_trace_data',
66                    default=True, action='store_false',
67                    help='Tell the device not to send the trace data in '
68                    'compressed form.')
69  parser.add_option('--link-assets', dest='link_assets', default=False,
70                    action='store_true',
71                    help='(deprecated)')
72  parser.add_option('--from-file', dest='from_file', action='store',
73                    help='read the trace from a file (compressed) rather than '
74                    'running a live trace')
75  parser.add_option('--asset-dir', dest='asset_dir', default='trace-viewer',
76                    type='string', help='(deprecated)')
77  parser.add_option('-e', '--serial', dest='device_serial', type='string',
78                    help='adb device serial number')
79  parser.add_option('--agent-dirs', dest='agent_dirs', type='string',
80                    help='the directories of additional systrace agent modules.'
81                    ' The directories should be comma separated, e.g., '
82                    '--agent-dirs=dir1,dir2,dir3. Directory |%s| is the default'
83                    ' agent directory and will always be checked.'
84                    % DEFAULT_AGENT_DIR)
85
86  options, categories = parser.parse_args(argv[1:])
87
88  if options.link_assets or options.asset_dir != 'trace-viewer':
89    parser.error('--link-assets and --asset-dir are deprecated.')
90
91  if (options.trace_time is not None) and (options.trace_time <= 0):
92    parser.error('the trace time must be a positive number')
93
94  if (options.trace_buf_size is not None) and (options.trace_buf_size <= 0):
95    parser.error('the trace buffer size must be a positive number')
96
97  return (options, categories)
98
99
100def write_trace_html(html_filename, script_dir, agents):
101  """Writes out a trace html file.
102
103  Args:
104    html_filename: The name of the file to write.
105    script_dir: The directory containing this script.
106    agents: The systrace agents.
107  """
108  html_prefix = read_asset(script_dir, 'prefix.html')
109  html_suffix = read_asset(script_dir, 'suffix.html')
110  trace_viewer_html = read_asset(script_dir, 'systrace_trace_viewer.html')
111
112  # Open the file in binary mode to prevent python from changing the
113  # line endings.
114  html_file = open(html_filename, 'wb')
115  html_file.write(html_prefix.replace('{{SYSTRACE_TRACE_VIEWER_HTML}}',
116                                      trace_viewer_html))
117
118  html_file.write('<!-- BEGIN TRACE -->\n')
119  for a in agents:
120    html_file.write('  <script class="')
121    html_file.write(a.get_class_name())
122    html_file.write('" type="application/text">\n')
123    html_file.write(a.get_trace_data())
124    html_file.write('  </script>\n')
125  html_file.write('<!-- END TRACE -->\n')
126
127  html_file.write(html_suffix)
128  html_file.close()
129  print '\n    wrote file://%s\n' % os.path.abspath(html_filename)
130
131
132def create_agents(options, categories):
133  """Create systrace agents.
134
135  This function will search systrace agent modules in agent directories and
136  create the corresponding systrace agents.
137  Args:
138    options: The command-line options.
139    categories: The trace categories to capture.
140  Returns:
141    The list of systrace agents.
142  """
143  agent_dirs = [os.path.join(os.path.dirname(__file__), DEFAULT_AGENT_DIR)]
144  if options.agent_dirs:
145    agent_dirs.extend(options.agent_dirs.split(','))
146
147  agents = []
148  for agent_dir in agent_dirs:
149    if not agent_dir:
150      continue
151    for filename in os.listdir(agent_dir):
152      (module_name, ext) = os.path.splitext(filename)
153      if ext != '.py' or module_name == '__init__':
154        continue
155      (f, pathname, data) = imp.find_module(module_name, [agent_dir])
156      try:
157        module = imp.load_module(module_name, f, pathname, data)
158      finally:
159        if f:
160          f.close()
161      if module:
162        agent = module.try_create_agent(options, categories)
163        if not agent:
164          continue
165        agents.append(agent)
166  return agents
167
168
169def main():
170  options, categories = parse_options(sys.argv)
171  agents = create_agents(options, categories)
172
173  if not agents:
174    dirs = DEFAULT_AGENT_DIR
175    if options.agent_dirs:
176      dirs += ',' + options.agent_dirs
177    print >> sys.stderr, ('No systrace agent is available in directories |%s|.'
178                          % dirs)
179    sys.exit(1)
180
181  for a in agents:
182    a.start()
183
184  for a in agents:
185    a.collect_result()
186    if not a.expect_trace():
187      # Nothing more to do.
188      return
189
190  script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
191  write_trace_html(options.output_file, script_dir, agents)
192
193
194def read_asset(src_dir, filename):
195  return open(os.path.join(src_dir, filename)).read()
196
197
198if __name__ == '__main__':
199  main()
200