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 flattened_css_file = 'style.css' 16 flattened_js_file = 'script.js' 17 18 class 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 34 def 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 54 def add_adb_serial(command, serial): 55 if serial != None: 56 command.insert(1, serial) 57 command.insert(1, '-s') 58 59 def 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 82 parser.add_option('--link-assets', dest='link_assets', default=False, 83 action='store_true', help='link to original CSS or JS resources ' 84 'instead of embedding them') 85 parser.add_option('--from-file', dest='from_file', action='store', 86 help='read the trace from a file (compressed) rather than running a live trace') 87 parser.add_option('--asset-dir', dest='asset_dir', default='trace-viewer', 88 type='string', help='') 89 parser.add_option('-e', '--serial', dest='device_serial', type='string', 90 help='adb device serial number') 91 92 options, args = parser.parse_args() 93 94 if options.list_categories: 95 atrace_args = ['adb', 'shell', 'atrace', '--list_categories'] 96 expect_trace = False 97 elif options.from_file is not None: 98 atrace_args = ['cat', options.from_file] 99 expect_trace = True 100 else: 101 atrace_args = ['adb', 'shell', 'atrace', '-z'] 102 expect_trace = True 103 104 if options.trace_time is not None: 105 if options.trace_time > 0: 106 atrace_args.extend(['-t', str(options.trace_time)]) 107 else: 108 parser.error('the trace time must be a positive number') 109 110 if options.trace_buf_size is not None: 111 if options.trace_buf_size > 0: 112 atrace_args.extend(['-b', str(options.trace_buf_size)]) 113 else: 114 parser.error('the trace buffer size must be a positive number') 115 116 if options.app_name is not None: 117 atrace_args.extend(['-a', options.app_name]) 118 119 if options.kfuncs is not None: 120 atrace_args.extend(['-k', options.kfuncs]) 121 122 atrace_args.extend(args) 123 124 if atrace_args[0] == 'adb': 125 add_adb_serial(atrace_args, options.device_serial) 126 127 script_dir = os.path.dirname(os.path.abspath(sys.argv[0])) 128 129 if options.link_assets: 130 src_dir = os.path.join(script_dir, options.asset_dir, 'src') 131 build_dir = os.path.join(script_dir, options.asset_dir, 'build') 132 133 js_files, js_flattenizer, css_files = get_assets(src_dir, build_dir) 134 135 css = '\n'.join(linked_css_tag % (os.path.join(src_dir, f)) for f in css_files) 136 js = '<script language="javascript">\n%s</script>\n' % js_flattenizer 137 js += '\n'.join(linked_js_tag % (os.path.join(src_dir, f)) for f in js_files) 138 else: 139 css_filename = os.path.join(script_dir, flattened_css_file) 140 js_filename = os.path.join(script_dir, flattened_js_file) 141 css = compiled_css_tag % (open(css_filename).read()) 142 js = compiled_js_tag % (open(js_filename).read()) 143 144 html_filename = options.output_file 145 146 adb = subprocess.Popen(atrace_args, stdout=subprocess.PIPE, 147 stderr=subprocess.PIPE) 148 149 result = None 150 data = [] 151 152 # Read the text portion of the output and watch for the 'TRACE:' marker that 153 # indicates the start of the trace data. 154 while result is None: 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 = os.read(adb.stdout.fileno(), 4096) 162 parts = out.split('\nTRACE:', 1) 163 164 txt = parts[0].replace('\r', '') 165 if len(parts) == 2: 166 # The '\nTRACE:' match stole the last newline from the text, so add it 167 # back here. 168 txt += '\n' 169 sys.stdout.write(txt) 170 sys.stdout.flush() 171 172 if len(parts) == 2: 173 data.append(parts[1]) 174 sys.stdout.write("downloading trace...") 175 sys.stdout.flush() 176 break 177 178 result = adb.poll() 179 180 # Read and buffer the data portion of the output. 181 while True: 182 ready = select.select([adb.stdout, adb.stderr], [], [adb.stdout, adb.stderr]) 183 keepReading = False 184 if adb.stderr in ready[0]: 185 err = os.read(adb.stderr.fileno(), 4096) 186 if len(err) > 0: 187 keepReading = True 188 sys.stderr.write(err) 189 sys.stderr.flush() 190 if adb.stdout in ready[0]: 191 out = os.read(adb.stdout.fileno(), 4096) 192 if len(out) > 0: 193 keepReading = True 194 data.append(out) 195 196 if result is not None and not keepReading: 197 break 198 199 result = adb.poll() 200 201 if result == 0: 202 if expect_trace: 203 data = ''.join(data) 204 205 # Collapse CRLFs that are added by adb shell. 206 if data.startswith('\r\n'): 207 data = data.replace('\r\n', '\n') 208 209 # Skip the initial newline. 210 data = data[1:] 211 212 if not data: 213 print >> sys.stderr, ('No data was captured. Output file was not ' + 214 'written.') 215 sys.exit(1) 216 else: 217 # Indicate to the user that the data download is complete. 218 print " done\n" 219 220 html_file = open(html_filename, 'w') 221 html_file.write(html_prefix % (css, js)) 222 223 size = 4096 224 dec = zlib.decompressobj() 225 for chunk in (data[i:i+size] for i in xrange(0, len(data), size)): 226 decoded_chunk = dec.decompress(chunk) 227 html_chunk = decoded_chunk.replace('\n', '\\n\\\n') 228 html_file.write(html_chunk) 229 230 html_out = dec.flush().replace('\n', '\\n\\\n') 231 html_file.write(html_out) 232 html_file.write(html_suffix) 233 html_file.close() 234 print "\n wrote file://%s/%s\n" % (os.getcwd(), options.output_file) 235 236 else: # i.e. result != 0 237 print >> sys.stderr, 'adb returned error code %d' % result 238 sys.exit(1) 239 240 def get_assets(src_dir, build_dir): 241 sys.path.append(build_dir) 242 gen = __import__('generate_standalone_timeline_view', {}, {}) 243 parse_deps = __import__('parse_deps', {}, {}) 244 filenames = gen._get_input_filenames() 245 load_sequence = parse_deps.calc_load_sequence(filenames, src_dir) 246 247 js_files = [] 248 js_flattenizer = "window.FLATTENED = {};\n" 249 css_files = [] 250 251 for module in load_sequence: 252 js_files.append(os.path.relpath(module.filename, src_dir)) 253 js_flattenizer += "window.FLATTENED['%s'] = true;\n" % module.name 254 for style_sheet in module.style_sheets: 255 css_files.append(os.path.relpath(style_sheet.filename, src_dir)) 256 257 sys.path.pop() 258 259 return (js_files, js_flattenizer, css_files) 260 261 html_prefix = """<!DOCTYPE HTML> 262 <html> 263 <head i18n-values="dir:textdirection;"> 264 <meta charset="utf-8"/> 265 <title>Android System Trace</title> 266 %s 267 %s 268 <script language="javascript"> 269 document.addEventListener('DOMContentLoaded', function() { 270 if (!linuxPerfData) 271 return; 272 273 var m = new tracing.Model(linuxPerfData); 274 var timelineViewEl = document.querySelector('.view'); 275 tracing.ui.decorate(timelineViewEl, tracing.TimelineView); 276 timelineViewEl.model = m; 277 timelineViewEl.tabIndex = 1; 278 timelineViewEl.timeline.focusElement = timelineViewEl; 279 }); 280 </script> 281 <style> 282 .view { 283 overflow: hidden; 284 position: absolute; 285 top: 0; 286 bottom: 0; 287 left: 0; 288 right: 0; 289 } 290 </style> 291 </head> 292 <body> 293 <div class="view"> 294 </div> 295 <!-- BEGIN TRACE --> 296 <script> 297 var linuxPerfData = "\\ 298 """ 299 300 html_suffix = """\\n"; 301 </script> 302 <!-- END TRACE --> 303 </body> 304 </html> 305 """ 306 307 compiled_css_tag = """<style type="text/css">%s</style>""" 308 compiled_js_tag = """<script language="javascript">%s</script>""" 309 310 linked_css_tag = """<link rel="stylesheet" href="%s"></link>""" 311 linked_js_tag = """<script language="javascript" src="%s"></script>""" 312 313 if __name__ == '__main__': 314 main() 315