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, re, 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 parser.add_option('--no-fix-threads', dest='fix_threads', default=True, 82 action='store_false', help='don\'t fix missing or truncated thread names') 83 84 parser.add_option('--link-assets', dest='link_assets', default=False, 85 action='store_true', help='link to original CSS or JS resources ' 86 'instead of embedding them') 87 parser.add_option('--from-file', dest='from_file', action='store', 88 help='read the trace from a file (compressed) rather than running a live trace') 89 parser.add_option('--asset-dir', dest='asset_dir', default='trace-viewer', 90 type='string', help='') 91 parser.add_option('-e', '--serial', dest='device_serial', type='string', 92 help='adb device serial number') 93 94 options, args = parser.parse_args() 95 96 if options.list_categories: 97 atrace_args = ['adb', 'shell', 'atrace', '--list_categories'] 98 expect_trace = False 99 elif options.from_file is not None: 100 atrace_args = ['cat', options.from_file] 101 expect_trace = True 102 else: 103 atrace_args = ['adb', 'shell', 'atrace', '-z'] 104 expect_trace = True 105 106 if options.trace_time is not None: 107 if options.trace_time > 0: 108 atrace_args.extend(['-t', str(options.trace_time)]) 109 else: 110 parser.error('the trace time must be a positive number') 111 112 if options.trace_buf_size is not None: 113 if options.trace_buf_size > 0: 114 atrace_args.extend(['-b', str(options.trace_buf_size)]) 115 else: 116 parser.error('the trace buffer size must be a positive number') 117 118 if options.app_name is not None: 119 atrace_args.extend(['-a', options.app_name]) 120 121 if options.kfuncs is not None: 122 atrace_args.extend(['-k', options.kfuncs]) 123 124 atrace_args.extend(args) 125 126 if options.fix_threads: 127 atrace_args.extend([';', 'ps', '-t']) 128 129 if atrace_args[0] == 'adb': 130 add_adb_serial(atrace_args, options.device_serial) 131 132 script_dir = os.path.dirname(os.path.abspath(sys.argv[0])) 133 134 if options.link_assets: 135 src_dir = os.path.join(script_dir, options.asset_dir, 'src') 136 build_dir = os.path.join(script_dir, options.asset_dir, 'build') 137 138 js_files, js_flattenizer, css_files, templates = get_assets(src_dir, build_dir) 139 140 css = '\n'.join(linked_css_tag % (os.path.join(src_dir, f)) for f in css_files) 141 js = '<script language="javascript">\n%s</script>\n' % js_flattenizer 142 js += '\n'.join(linked_js_tag % (os.path.join(src_dir, f)) for f in js_files) 143 144 else: 145 css_filename = os.path.join(script_dir, flattened_css_file) 146 js_filename = os.path.join(script_dir, flattened_js_file) 147 css = compiled_css_tag % (open(css_filename).read()) 148 js = compiled_js_tag % (open(js_filename).read()) 149 templates = '' 150 151 html_filename = options.output_file 152 153 adb = subprocess.Popen(atrace_args, stdout=subprocess.PIPE, 154 stderr=subprocess.PIPE) 155 156 result = None 157 data = [] 158 159 # Read the text portion of the output and watch for the 'TRACE:' marker that 160 # indicates the start of the trace data. 161 while result is None: 162 ready = select.select([adb.stdout, adb.stderr], [], [adb.stdout, adb.stderr]) 163 if adb.stderr in ready[0]: 164 err = os.read(adb.stderr.fileno(), 4096) 165 sys.stderr.write(err) 166 sys.stderr.flush() 167 if adb.stdout in ready[0]: 168 out = os.read(adb.stdout.fileno(), 4096) 169 parts = out.split('\nTRACE:', 1) 170 171 txt = parts[0].replace('\r', '') 172 if len(parts) == 2: 173 # The '\nTRACE:' match stole the last newline from the text, so add it 174 # back here. 175 txt += '\n' 176 sys.stdout.write(txt) 177 sys.stdout.flush() 178 179 if len(parts) == 2: 180 data.append(parts[1]) 181 sys.stdout.write("downloading trace...") 182 sys.stdout.flush() 183 break 184 185 result = adb.poll() 186 187 # Read and buffer the data portion of the output. 188 while True: 189 ready = select.select([adb.stdout, adb.stderr], [], [adb.stdout, adb.stderr]) 190 keepReading = False 191 if adb.stderr in ready[0]: 192 err = os.read(adb.stderr.fileno(), 4096) 193 if len(err) > 0: 194 keepReading = True 195 sys.stderr.write(err) 196 sys.stderr.flush() 197 if adb.stdout in ready[0]: 198 out = os.read(adb.stdout.fileno(), 4096) 199 if len(out) > 0: 200 keepReading = True 201 data.append(out) 202 203 if result is not None and not keepReading: 204 break 205 206 result = adb.poll() 207 208 if result == 0: 209 if expect_trace: 210 data = ''.join(data) 211 212 # Collapse CRLFs that are added by adb shell. 213 if data.startswith('\r\n'): 214 data = data.replace('\r\n', '\n') 215 216 # Skip the initial newline. 217 data = data[1:] 218 219 if not data: 220 print >> sys.stderr, ('No data was captured. Output file was not ' + 221 'written.') 222 sys.exit(1) 223 else: 224 # Indicate to the user that the data download is complete. 225 print " done\n" 226 227 # Extract the thread list dumped by ps. 228 threads = {} 229 if options.fix_threads: 230 parts = data.split('USER PID PPID VSIZE RSS WCHAN PC NAME', 1) 231 if len(parts) == 2: 232 data = parts[0] 233 for line in parts[1].splitlines(): 234 cols = line.split(None, 8) 235 if len(cols) == 9: 236 tid = int(cols[1]) 237 name = cols[8] 238 threads[tid] = name 239 240 # Decompress and preprocess the data. 241 out = zlib.decompress(data) 242 if options.fix_threads: 243 def repl(m): 244 tid = int(m.group(2)) 245 if tid > 0: 246 name = threads.get(tid) 247 if name is None: 248 name = m.group(1) 249 if name == '<...>': 250 name = '<' + str(tid) + '>' 251 threads[tid] = name 252 return name + '-' + m.group(2) 253 else: 254 return m.group(0) 255 out = re.sub(r'^\s*(\S+)-(\d+)', repl, out, flags=re.MULTILINE) 256 257 html_prefix = read_asset(script_dir, 'prefix.html') 258 html_suffix = read_asset(script_dir, 'suffix.html') 259 260 html_file = open(html_filename, 'w') 261 html_file.write(html_prefix % (css, js, templates)) 262 html_out = out.replace('\n', '\\n\\\n') 263 html_file.write(html_out) 264 html_file.write(html_suffix) 265 html_file.close() 266 print "\n wrote file://%s\n" % os.path.abspath(options.output_file) 267 268 else: # i.e. result != 0 269 print >> sys.stderr, 'adb returned error code %d' % result 270 sys.exit(1) 271 272def read_asset(src_dir, filename): 273 return open(os.path.join(src_dir, filename)).read() 274 275def get_assets(src_dir, build_dir): 276 sys.path.append(build_dir) 277 gen = __import__('generate_standalone_timeline_view', {}, {}) 278 parse_deps = __import__('parse_deps', {}, {}) 279 gen_templates = __import__('generate_template_contents', {}, {}) 280 filenames = gen._get_input_filenames() 281 load_sequence = parse_deps.calc_load_sequence(filenames, src_dir) 282 283 js_files = [] 284 js_flattenizer = "window.FLATTENED = {};\n" 285 js_flattenizer += "window.FLATTENED_RAW_SCRIPTS = {};\n" 286 css_files = [] 287 288 for module in load_sequence: 289 js_files.append(os.path.relpath(module.filename, src_dir)) 290 js_flattenizer += "window.FLATTENED['%s'] = true;\n" % module.name 291 for dependent_raw_script_name in module.dependent_raw_script_names: 292 js_flattenizer += ( 293 "window.FLATTENED_RAW_SCRIPTS['%s'] = true;\n" % 294 dependent_raw_script_name) 295 296 for style_sheet in module.style_sheets: 297 css_files.append(os.path.relpath(style_sheet.filename, src_dir)) 298 299 templates = gen_templates.generate_templates() 300 301 sys.path.pop() 302 303 return (js_files, js_flattenizer, css_files, templates) 304 305 306compiled_css_tag = """<style type="text/css">%s</style>""" 307compiled_js_tag = """<script language="javascript">%s</script>""" 308 309linked_css_tag = """<link rel="stylesheet" href="%s"></link>""" 310linked_js_tag = """<script language="javascript" src="%s"></script>""" 311 312if __name__ == '__main__': 313 main() 314