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 9 This is a tool for capturing a trace that includes data from both userland and 10 the kernel. It creates an HTML file for visualizing the trace. 11 """ 12 13 import errno, optparse, os, select, subprocess, sys, time, zlib 14 15 # This list is based on the tags in frameworks/native/include/utils/Trace.h. 16 trace_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 29 flattened_css_file = 'style.css' 30 flattened_js_file = 'script.js' 31 32 def add_adb_serial(command, serial): 33 if serial != None: 34 command.insert(1, serial) 35 command.insert(1, '-s') 36 37 def 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 = 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 else: 142 css_filename = os.path.join(script_dir, flattened_css_file) 143 js_filename = os.path.join(script_dir, flattened_js_file) 144 css = compiled_css_tag % (open(css_filename).read()) 145 js = compiled_js_tag % (open(js_filename).read()) 146 147 html_filename = options.output_file 148 149 trace_started = False 150 leftovers = '' 151 adb = subprocess.Popen(atrace_args, stdout=subprocess.PIPE, 152 stderr=subprocess.PIPE) 153 dec = zlib.decompressobj() 154 while True: 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 = leftovers + os.read(adb.stdout.fileno(), 4096) 162 if options.from_file is None: 163 out = out.replace('\r\n', '\n') 164 if out.endswith('\r'): 165 out = out[:-1] 166 leftovers = '\r' 167 else: 168 leftovers = '' 169 if not trace_started: 170 lines = out.splitlines(True) 171 out = '' 172 for i, line in enumerate(lines): 173 if line == 'TRACE:\n': 174 sys.stdout.write("downloading trace...") 175 sys.stdout.flush() 176 out = ''.join(lines[i+1:]) 177 html_file = open(html_filename, 'w') 178 html_file.write(html_prefix % (css, js)) 179 trace_started = True 180 break 181 elif 'TRACE:'.startswith(line) and i == len(lines) - 1: 182 leftovers = line + leftovers 183 else: 184 sys.stdout.write(line) 185 sys.stdout.flush() 186 if len(out) > 0: 187 out = dec.decompress(out) 188 html_out = out.replace('\n', '\\n\\\n') 189 if len(html_out) > 0: 190 html_file.write(html_out) 191 result = adb.poll() 192 if result is not None: 193 break 194 if result != 0: 195 print >> sys.stderr, 'adb returned error code %d' % result 196 elif trace_started: 197 html_out = dec.flush().replace('\n', '\\n\\\n').replace('\r', '') 198 if len(html_out) > 0: 199 html_file.write(html_out) 200 html_file.write(html_suffix) 201 html_file.close() 202 print " done\n\n wrote file://%s/%s\n" % (os.getcwd(), options.output_file) 203 else: 204 print >> sys.stderr, ('An error occured while capturing the trace. Output ' + 205 'file was not written.') 206 207 def get_assets(src_dir, build_dir): 208 sys.path.append(build_dir) 209 gen = __import__('generate_standalone_timeline_view', {}, {}) 210 parse_deps = __import__('parse_deps', {}, {}) 211 filenames = gen._get_input_filenames() 212 load_sequence = parse_deps.calc_load_sequence(filenames) 213 214 js_files = [] 215 js_flattenizer = "window.FLATTENED = {};\n" 216 css_files = [] 217 218 for module in load_sequence: 219 js_files.append(os.path.relpath(module.filename, src_dir)) 220 js_flattenizer += "window.FLATTENED['%s'] = true;\n" % module.name 221 for style_sheet in module.style_sheets: 222 css_files.append(os.path.relpath(style_sheet.filename, src_dir)) 223 224 sys.path.pop() 225 226 return (js_files, js_flattenizer, css_files) 227 228 html_prefix = """<!DOCTYPE HTML> 229 <html> 230 <head i18n-values="dir:textdirection;"> 231 <title>Android System Trace</title> 232 %s 233 %s 234 <script language="javascript"> 235 document.addEventListener('DOMContentLoaded', function() { 236 if (!linuxPerfData) 237 return; 238 239 var m = new tracing.Model(linuxPerfData); 240 var timelineViewEl = document.querySelector('.view'); 241 tracing.ui.decorate(timelineViewEl, tracing.TimelineView); 242 timelineViewEl.model = m; 243 timelineViewEl.tabIndex = 1; 244 timelineViewEl.timeline.focusElement = timelineViewEl; 245 }); 246 </script> 247 <style> 248 .view { 249 overflow: hidden; 250 position: absolute; 251 top: 0; 252 bottom: 0; 253 left: 0; 254 right: 0; 255 } 256 </style> 257 </head> 258 <body> 259 <div class="view"> 260 </div> 261 <!-- BEGIN TRACE --> 262 <script> 263 var linuxPerfData = "\\ 264 """ 265 266 html_suffix = """\\n"; 267 </script> 268 <!-- END TRACE --> 269 </body> 270 </html> 271 """ 272 273 compiled_css_tag = """<style type="text/css">%s</style>""" 274 compiled_js_tag = """<script language="javascript">%s</script>""" 275 276 linked_css_tag = """<link rel="stylesheet" href="%s"></link>""" 277 linked_js_tag = """<script language="javascript" src="%s"></script>""" 278 279 if __name__ == '__main__': 280 main() 281