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_html_file = 'systrace_trace_viewer.html' 30 31def add_adb_serial(command, serial): 32 if serial != None: 33 command.insert(1, serial) 34 command.insert(1, '-s') 35 36def main(): 37 parser = optparse.OptionParser() 38 parser.add_option('-o', dest='output_file', help='write HTML to FILE', 39 default='trace.html', metavar='FILE') 40 parser.add_option('-t', '--time', dest='trace_time', type='int', 41 help='trace for N seconds', metavar='N') 42 parser.add_option('-b', '--buf-size', dest='trace_buf_size', type='int', 43 help='use a trace buffer size of N KB', metavar='N') 44 parser.add_option('-d', '--disk', dest='trace_disk', default=False, 45 action='store_true', help='trace disk I/O (requires root)') 46 parser.add_option('-f', '--cpu-freq', dest='trace_cpu_freq', default=False, 47 action='store_true', help='trace CPU frequency changes') 48 parser.add_option('-i', '--cpu-idle', dest='trace_cpu_idle', default=False, 49 action='store_true', help='trace CPU idle events') 50 parser.add_option('-l', '--cpu-load', dest='trace_cpu_load', default=False, 51 action='store_true', help='trace CPU load') 52 parser.add_option('-s', '--no-cpu-sched', dest='trace_cpu_sched', default=True, 53 action='store_false', help='inhibit tracing CPU ' + 54 'scheduler (allows longer trace times by reducing data ' + 55 'rate into buffer)') 56 parser.add_option('-u', '--bus-utilization', dest='trace_bus_utilization', 57 default=False, action='store_true', 58 help='trace bus utilization (requires root)') 59 parser.add_option('-w', '--workqueue', dest='trace_workqueue', default=False, 60 action='store_true', help='trace the kernel workqueues ' + 61 '(requires root)') 62 parser.add_option('--set-tags', dest='set_tags', action='store', 63 help='set the enabled trace tags and exit; set to a ' + 64 'comma separated list of: ' + 65 ', '.join(trace_tag_bits.iterkeys())) 66 parser.add_option('--link-assets', dest='link_assets', default=False, 67 action='store_true', help='link to original CSS or JS resources ' 68 'instead of embedding them') 69 parser.add_option('--from-file', dest='from_file', action='store', 70 help='read the trace from a file rather than running a live trace') 71 parser.add_option('--asset-dir', dest='asset_dir', default='trace-viewer', 72 type='string', help='') 73 parser.add_option('-e', '--serial', dest='device_serial', type='string', 74 help='adb device serial number') 75 options, args = parser.parse_args() 76 77 if options.link_assets or options.asset_dir != 'trace-viewer': 78 parser.error('--link-assets and --asset-dir is deprecated.') 79 80 if options.set_tags: 81 flags = 0 82 tags = options.set_tags.split(',') 83 for tag in tags: 84 try: 85 flags |= trace_tag_bits[tag] 86 except KeyError: 87 parser.error('unrecognized tag: %s\nknown tags are: %s' % 88 (tag, ', '.join(trace_tag_bits.iterkeys()))) 89 atrace_args = ['adb', 'shell', 'setprop', 'debug.atrace.tags.enableflags', hex(flags)] 90 add_adb_serial(atrace_args, options.device_serial) 91 try: 92 subprocess.check_call(atrace_args) 93 except subprocess.CalledProcessError, e: 94 print >> sys.stderr, 'unable to set tags: %s' % e 95 print '\nSet enabled tags to: %s\n' % ', '.join(tags) 96 print ('You will likely need to restart the Android framework for this to ' + 97 'take effect:\n\n adb shell stop\n adb shell ' + 98 'start\n') 99 return 100 101 atrace_args = ['adb', 'shell', 'atrace', '-z'] 102 add_adb_serial(atrace_args, options.device_serial) 103 104 if options.trace_disk: 105 atrace_args.append('-d') 106 if options.trace_cpu_freq: 107 atrace_args.append('-f') 108 if options.trace_cpu_idle: 109 atrace_args.append('-i') 110 if options.trace_cpu_load: 111 atrace_args.append('-l') 112 if options.trace_cpu_sched: 113 atrace_args.append('-s') 114 if options.trace_bus_utilization: 115 atrace_args.append('-u') 116 if options.trace_workqueue: 117 atrace_args.append('-w') 118 if options.trace_time is not None: 119 if options.trace_time > 0: 120 atrace_args.extend(['-t', str(options.trace_time)]) 121 else: 122 parser.error('the trace time must be a positive number') 123 if options.trace_buf_size is not None: 124 if options.trace_buf_size > 0: 125 atrace_args.extend(['-b', str(options.trace_buf_size)]) 126 else: 127 parser.error('the trace buffer size must be a positive number') 128 129 if options.from_file is not None: 130 atrace_args = ['cat', options.from_file] 131 132 script_dir = os.path.dirname(os.path.abspath(sys.argv[0])) 133 134 with open(os.path.join(script_dir, flattened_html_file), 'r') as f: 135 trace_viewer_html = f.read() 136 137 html_filename = options.output_file 138 139 trace_started = False 140 leftovers = '' 141 adb = subprocess.Popen(atrace_args, stdout=subprocess.PIPE, 142 stderr=subprocess.PIPE) 143 dec = zlib.decompressobj() 144 while True: 145 ready = select.select([adb.stdout, adb.stderr], [], [adb.stdout, adb.stderr]) 146 if adb.stderr in ready[0]: 147 err = os.read(adb.stderr.fileno(), 4096) 148 sys.stderr.write(err) 149 sys.stderr.flush() 150 if adb.stdout in ready[0]: 151 out = leftovers + os.read(adb.stdout.fileno(), 4096) 152 if options.from_file is None: 153 out = out.replace('\r\n', '\n') 154 if out.endswith('\r'): 155 out = out[:-1] 156 leftovers = '\r' 157 else: 158 leftovers = '' 159 if not trace_started: 160 lines = out.splitlines(True) 161 out = '' 162 for i, line in enumerate(lines): 163 if line == 'TRACE:\n': 164 sys.stdout.write("downloading trace...") 165 sys.stdout.flush() 166 out = ''.join(lines[i+1:]) 167 html_prefix = read_asset(script_dir, 'prefix.html') 168 html_file = open(html_filename, 'w') 169 html_file.write( 170 html_prefix.replace("{{SYSTRACE_TRACE_VIEWER_HTML}}", 171 trace_viewer_html)) 172 html_file.write('<!-- BEGIN TRACE -->\n' + 173 ' <script class="trace-data" type="application/text">\n') 174 trace_started = True 175 break 176 elif 'TRACE:'.startswith(line) and i == len(lines) - 1: 177 leftovers = line + leftovers 178 else: 179 sys.stdout.write(line) 180 sys.stdout.flush() 181 if len(out) > 0: 182 out = dec.decompress(out) 183 html_out = out.replace('\n', '\\n\\\n') 184 if len(html_out) > 0: 185 html_file.write(html_out) 186 result = adb.poll() 187 if result is not None: 188 break 189 if result != 0: 190 print >> sys.stderr, 'adb returned error code %d' % result 191 elif trace_started: 192 html_out = dec.flush().replace('\n', '\\n\\\n').replace('\r', '') 193 if len(html_out) > 0: 194 html_file.write(html_out) 195 html_file.write(' </script>\n<!-- END TRACE -->\n') 196 html_suffix = read_asset(script_dir, 'suffix.html') 197 html_file.write(html_suffix) 198 html_file.close() 199 print " done\n\n wrote file://%s\n" % (os.path.abspath(options.output_file)) 200 else: 201 print >> sys.stderr, ('An error occured while capturing the trace. Output ' + 202 'file was not written.') 203 204def read_asset(src_dir, filename): 205 return open(os.path.join(src_dir, filename)).read() 206 207if __name__ == '__main__': 208 main() 209