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_css_file = 'style.css' 30flattened_js_file = 'script.js' 31 32def add_adb_serial(command, serial): 33 if serial != None: 34 command.insert(1, serial) 35 command.insert(1, '-s') 36 37def main(): 38 parser = optparse.OptionParser() 39 parser.add_option('-o', dest='output_file', help='write HTML to FILE', 40 default='trace.html', metavar='FILE') 41 parser.add_option('-t', '--time', dest='trace_time', type='int', 42 help='trace for N seconds', metavar='N') 43 parser.add_option('-b', '--buf-size', dest='trace_buf_size', type='int', 44 help='use a trace buffer size of N KB', metavar='N') 45 parser.add_option('-d', '--disk', dest='trace_disk', default=False, 46 action='store_true', help='trace disk I/O (requires root)') 47 parser.add_option('-f', '--cpu-freq', dest='trace_cpu_freq', default=False, 48 action='store_true', help='trace CPU frequency changes') 49 parser.add_option('-i', '--cpu-idle', dest='trace_cpu_idle', default=False, 50 action='store_true', help='trace CPU idle events') 51 parser.add_option('-l', '--cpu-load', dest='trace_cpu_load', default=False, 52 action='store_true', help='trace CPU load') 53 parser.add_option('-s', '--no-cpu-sched', dest='trace_cpu_sched', default=True, 54 action='store_false', help='inhibit tracing CPU ' + 55 'scheduler (allows longer trace times by reducing data ' + 56 'rate into buffer)') 57 parser.add_option('-u', '--bus-utilization', dest='trace_bus_utilization', 58 default=False, action='store_true', 59 help='trace bus utilization (requires root)') 60 parser.add_option('-w', '--workqueue', dest='trace_workqueue', default=False, 61 action='store_true', help='trace the kernel workqueues ' + 62 '(requires root)') 63 parser.add_option('--set-tags', dest='set_tags', action='store', 64 help='set the enabled trace tags and exit; set to a ' + 65 'comma separated list of: ' + 66 ', '.join(trace_tag_bits.iterkeys())) 67 parser.add_option('--link-assets', dest='link_assets', default=False, 68 action='store_true', help='link to original CSS or JS resources ' 69 'instead of embedding them') 70 parser.add_option('--from-file', dest='from_file', action='store', 71 help='read the trace from a file rather than running a live trace') 72 parser.add_option('--asset-dir', dest='asset_dir', default='trace-viewer', 73 type='string', help='') 74 parser.add_option('-e', '--serial', dest='device_serial', type='string', 75 help='adb device serial number') 76 options, args = parser.parse_args() 77 78 if options.set_tags: 79 flags = 0 80 tags = options.set_tags.split(',') 81 for tag in tags: 82 try: 83 flags |= trace_tag_bits[tag] 84 except KeyError: 85 parser.error('unrecognized tag: %s\nknown tags are: %s' % 86 (tag, ', '.join(trace_tag_bits.iterkeys()))) 87 atrace_args = ['adb', 'shell', 'setprop', 'debug.atrace.tags.enableflags', hex(flags)] 88 add_adb_serial(atrace_args, options.device_serial) 89 try: 90 subprocess.check_call(atrace_args) 91 except subprocess.CalledProcessError, e: 92 print >> sys.stderr, 'unable to set tags: %s' % e 93 print '\nSet enabled tags to: %s\n' % ', '.join(tags) 94 print ('You will likely need to restart the Android framework for this to ' + 95 'take effect:\n\n adb shell stop\n adb shell ' + 96 'start\n') 97 return 98 99 atrace_args = ['adb', 'shell', 'atrace', '-z'] 100 add_adb_serial(atrace_args, options.device_serial) 101 102 if options.trace_disk: 103 atrace_args.append('-d') 104 if options.trace_cpu_freq: 105 atrace_args.append('-f') 106 if options.trace_cpu_idle: 107 atrace_args.append('-i') 108 if options.trace_cpu_load: 109 atrace_args.append('-l') 110 if options.trace_cpu_sched: 111 atrace_args.append('-s') 112 if options.trace_bus_utilization: 113 atrace_args.append('-u') 114 if options.trace_workqueue: 115 atrace_args.append('-w') 116 if options.trace_time is not None: 117 if options.trace_time > 0: 118 atrace_args.extend(['-t', str(options.trace_time)]) 119 else: 120 parser.error('the trace time must be a positive number') 121 if options.trace_buf_size is not None: 122 if options.trace_buf_size > 0: 123 atrace_args.extend(['-b', str(options.trace_buf_size)]) 124 else: 125 parser.error('the trace buffer size must be a positive number') 126 127 if options.from_file is not None: 128 atrace_args = ['cat', options.from_file] 129 130 script_dir = os.path.dirname(os.path.abspath(sys.argv[0])) 131 132 if options.link_assets: 133 src_dir = os.path.join(script_dir, options.asset_dir, 'src') 134 build_dir = os.path.join(script_dir, options.asset_dir, 'build') 135 136 js_files, js_flattenizer, css_files, templates = get_assets(src_dir, build_dir) 137 138 css = '\n'.join(linked_css_tag % (os.path.join(src_dir, f)) for f in css_files) 139 js = '<script language="javascript">\n%s</script>\n' % js_flattenizer 140 js += '\n'.join(linked_js_tag % (os.path.join(src_dir, f)) for f in js_files) 141 142 else: 143 css_filename = os.path.join(script_dir, flattened_css_file) 144 js_filename = os.path.join(script_dir, flattened_js_file) 145 css = compiled_css_tag % (open(css_filename).read()) 146 js = compiled_js_tag % (open(js_filename).read()) 147 templates = '' 148 149 html_filename = options.output_file 150 151 trace_started = False 152 leftovers = '' 153 adb = subprocess.Popen(atrace_args, stdout=subprocess.PIPE, 154 stderr=subprocess.PIPE) 155 dec = zlib.decompressobj() 156 while True: 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 = leftovers + os.read(adb.stdout.fileno(), 4096) 164 if options.from_file is None: 165 out = out.replace('\r\n', '\n') 166 if out.endswith('\r'): 167 out = out[:-1] 168 leftovers = '\r' 169 else: 170 leftovers = '' 171 if not trace_started: 172 lines = out.splitlines(True) 173 out = '' 174 for i, line in enumerate(lines): 175 if line == 'TRACE:\n': 176 sys.stdout.write("downloading trace...") 177 sys.stdout.flush() 178 out = ''.join(lines[i+1:]) 179 html_prefix = read_asset(script_dir, 'prefix.html') 180 html_file = open(html_filename, 'w') 181 html_file.write(html_prefix % (css, js, templates)) 182 trace_started = True 183 break 184 elif 'TRACE:'.startswith(line) and i == len(lines) - 1: 185 leftovers = line + leftovers 186 else: 187 sys.stdout.write(line) 188 sys.stdout.flush() 189 if len(out) > 0: 190 out = dec.decompress(out) 191 html_out = out.replace('\n', '\\n\\\n') 192 if len(html_out) > 0: 193 html_file.write(html_out) 194 result = adb.poll() 195 if result is not None: 196 break 197 if result != 0: 198 print >> sys.stderr, 'adb returned error code %d' % result 199 elif trace_started: 200 html_out = dec.flush().replace('\n', '\\n\\\n').replace('\r', '') 201 if len(html_out) > 0: 202 html_file.write(html_out) 203 html_suffix = read_asset(script_dir, 'suffix.html') 204 html_file.write(html_suffix) 205 html_file.close() 206 print " done\n\n wrote file://%s\n" % (os.path.abspath(options.output_file)) 207 else: 208 print >> sys.stderr, ('An error occured while capturing the trace. Output ' + 209 'file was not written.') 210 211def read_asset(src_dir, filename): 212 return open(os.path.join(src_dir, filename)).read() 213 214def get_assets(src_dir, build_dir): 215 sys.path.append(build_dir) 216 gen = __import__('generate_standalone_timeline_view', {}, {}) 217 parse_deps = __import__('parse_deps', {}, {}) 218 gen_templates = __import__('generate_template_contents', {}, {}) 219 filenames = gen._get_input_filenames() 220 load_sequence = parse_deps.calc_load_sequence(filenames, src_dir) 221 222 js_files = [] 223 js_flattenizer = "window.FLATTENED = {};\n" 224 js_flattenizer += "window.FLATTENED_RAW_SCRIPTS = {};\n" 225 css_files = [] 226 227 for module in load_sequence: 228 js_files.append(os.path.relpath(module.filename, src_dir)) 229 js_flattenizer += "window.FLATTENED['%s'] = true;\n" % module.name 230 for dependent_raw_script_name in module.dependent_raw_script_names: 231 js_flattenizer += ( 232 "window.FLATTENED_RAW_SCRIPTS['%s'] = true;\n" % 233 dependent_raw_script_name) 234 235 for style_sheet in module.style_sheets: 236 css_files.append(os.path.relpath(style_sheet.filename, src_dir)) 237 238 templates = gen_templates.generate_templates() 239 240 sys.path.pop() 241 242 return (js_files, js_flattenizer, css_files, templates) 243 244compiled_css_tag = """<style type="text/css">%s</style>""" 245compiled_js_tag = """<script language="javascript">%s</script>""" 246 247linked_css_tag = """<link rel="stylesheet" href="%s"></link>""" 248linked_js_tag = """<script language="javascript" src="%s"></script>""" 249 250if __name__ == '__main__': 251 main() 252