1#!/usr/bin/env python3 2########################################################################## 3# 4# Copyright 2008 VMware, Inc. 5# All Rights Reserved. 6# 7# Permission is hereby granted, free of charge, to any person obtaining a 8# copy of this software and associated documentation files (the 9# "Software"), to deal in the Software without restriction, including 10# without limitation the rights to use, copy, modify, merge, publish, 11# distribute, sub license, and/or sell copies of the Software, and to 12# permit persons to whom the Software is furnished to do so, subject to 13# the following conditions: 14# 15# The above copyright notice and this permission notice (including the 16# next paragraph) shall be included in all copies or substantial portions 17# of the Software. 18# 19# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 20# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. 22# IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR 23# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26# 27########################################################################## 28 29 30import io 31import sys 32import xml.parsers.expat as xpat 33import argparse 34 35import format 36from model import * 37 38 39trace_ignore_calls = set(( 40 ("pipe_screen", "is_format_supported"), 41 ("pipe_screen", "get_name"), 42 ("pipe_screen", "get_vendor"), 43 ("pipe_screen", "get_param"), 44 ("pipe_screen", "get_paramf"), 45 ("pipe_screen", "get_shader_param"), 46 ("pipe_screen", "get_compute_param"), 47 ("pipe_screen", "get_disk_shader_cache"), 48)) 49 50 51def trace_call_ignore(call): 52 return (call.klass, call.method) in trace_ignore_calls 53 54 55ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF = range(4) 56 57 58class XmlToken: 59 60 def __init__(self, type, name_or_data, attrs = None, line = None, column = None): 61 assert type in (ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF) 62 self.type = type 63 self.name_or_data = name_or_data 64 self.attrs = attrs 65 self.line = line 66 self.column = column 67 68 def __str__(self): 69 if self.type == ELEMENT_START: 70 return '<' + self.name_or_data + ' ...>' 71 if self.type == ELEMENT_END: 72 return '</' + self.name_or_data + '>' 73 if self.type == CHARACTER_DATA: 74 return self.name_or_data 75 if self.type == EOF: 76 return 'end of file' 77 assert 0 78 79 80class XmlTokenizer: 81 """Expat based XML tokenizer.""" 82 83 def __init__(self, fp, skip_ws = True): 84 self.fp = fp 85 self.tokens = [] 86 self.index = 0 87 self.final = False 88 self.skip_ws = skip_ws 89 90 self.character_pos = 0, 0 91 self.character_data = [] 92 93 self.parser = xpat.ParserCreate() 94 self.parser.StartElementHandler = self.handle_element_start 95 self.parser.EndElementHandler = self.handle_element_end 96 self.parser.CharacterDataHandler = self.handle_character_data 97 98 def handle_element_start(self, name, attributes): 99 self.finish_character_data() 100 line, column = self.pos() 101 token = XmlToken(ELEMENT_START, name, attributes, line, column) 102 self.tokens.append(token) 103 104 def handle_element_end(self, name): 105 self.finish_character_data() 106 line, column = self.pos() 107 token = XmlToken(ELEMENT_END, name, None, line, column) 108 self.tokens.append(token) 109 110 def handle_character_data(self, data): 111 if not self.character_data: 112 self.character_pos = self.pos() 113 self.character_data.append(data) 114 115 def finish_character_data(self): 116 if self.character_data: 117 character_data = ''.join(self.character_data) 118 if not self.skip_ws or not character_data.isspace(): 119 line, column = self.character_pos 120 token = XmlToken(CHARACTER_DATA, character_data, None, line, column) 121 self.tokens.append(token) 122 self.character_data = [] 123 124 def next(self): 125 size = 16*1024 126 while self.index >= len(self.tokens) and not self.final: 127 self.tokens = [] 128 self.index = 0 129 data = self.fp.read(size) 130 self.final = len(data) < size 131 data = data.rstrip('\0') 132 try: 133 self.parser.Parse(data, self.final) 134 except xpat.ExpatError as e: 135 #if e.code == xpat.errors.XML_ERROR_NO_ELEMENTS: 136 if e.code == 3: 137 pass 138 else: 139 raise e 140 if self.index >= len(self.tokens): 141 line, column = self.pos() 142 token = XmlToken(EOF, None, None, line, column) 143 else: 144 token = self.tokens[self.index] 145 self.index += 1 146 return token 147 148 def pos(self): 149 return self.parser.CurrentLineNumber, self.parser.CurrentColumnNumber 150 151 152class TokenMismatch(Exception): 153 154 def __init__(self, expected, found): 155 self.expected = expected 156 self.found = found 157 158 def __str__(self): 159 return '%u:%u: %s expected, %s found' % (self.found.line, self.found.column, str(self.expected), str(self.found)) 160 161 162 163class XmlParser: 164 """Base XML document parser.""" 165 166 def __init__(self, fp): 167 self.tokenizer = XmlTokenizer(fp) 168 self.consume() 169 170 def consume(self): 171 self.token = self.tokenizer.next() 172 173 def match_element_start(self, name): 174 return self.token.type == ELEMENT_START and self.token.name_or_data == name 175 176 def match_element_end(self, name): 177 return self.token.type == ELEMENT_END and self.token.name_or_data == name 178 179 def element_start(self, name): 180 while self.token.type == CHARACTER_DATA: 181 self.consume() 182 if self.token.type != ELEMENT_START: 183 raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token) 184 if self.token.name_or_data != name: 185 raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token) 186 attrs = self.token.attrs 187 self.consume() 188 return attrs 189 190 def element_end(self, name): 191 while self.token.type == CHARACTER_DATA: 192 self.consume() 193 if self.token.type != ELEMENT_END: 194 raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token) 195 if self.token.name_or_data != name: 196 raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token) 197 self.consume() 198 199 def character_data(self, strip = True): 200 data = '' 201 while self.token.type == CHARACTER_DATA: 202 data += self.token.name_or_data 203 self.consume() 204 if strip: 205 data = data.strip() 206 return data 207 208 209class TraceParser(XmlParser): 210 211 def __init__(self, fp, options, state): 212 XmlParser.__init__(self, fp) 213 self.last_call_no = 0 214 self.state = state 215 self.options = options 216 217 def parse(self): 218 self.element_start('trace') 219 while self.token.type not in (ELEMENT_END, EOF): 220 call = self.parse_call() 221 call.is_junk = trace_call_ignore(call) 222 self.handle_call(call) 223 if self.token.type != EOF: 224 self.element_end('trace') 225 226 def parse_call(self): 227 attrs = self.element_start('call') 228 try: 229 no = int(attrs['no']) 230 except KeyError as e: 231 self.last_call_no += 1 232 no = self.last_call_no 233 else: 234 self.last_call_no = no 235 klass = attrs['class'] 236 method = attrs['method'] 237 args = [] 238 ret = None 239 time = None 240 while self.token.type == ELEMENT_START: 241 if self.token.name_or_data == 'arg': 242 arg = self.parse_arg() 243 args.append(arg) 244 elif self.token.name_or_data == 'ret': 245 ret = self.parse_ret() 246 elif self.token.name_or_data == 'call': 247 # ignore nested function calls 248 self.parse_call() 249 elif self.token.name_or_data == 'time': 250 time = self.parse_time() 251 else: 252 raise TokenMismatch("<arg ...> or <ret ...>", self.token) 253 self.element_end('call') 254 255 return Call(no, klass, method, args, ret, time) 256 257 def parse_arg(self): 258 attrs = self.element_start('arg') 259 name = attrs['name'] 260 value = self.parse_value(name) 261 self.element_end('arg') 262 263 return name, value 264 265 def parse_ret(self): 266 attrs = self.element_start('ret') 267 value = self.parse_value('ret') 268 self.element_end('ret') 269 270 return value 271 272 def parse_time(self): 273 attrs = self.element_start('time') 274 time = self.parse_value('time'); 275 self.element_end('time') 276 return time 277 278 def parse_value(self, name): 279 expected_tokens = ('null', 'bool', 'int', 'uint', 'float', 'string', 'enum', 'array', 'struct', 'ptr', 'bytes') 280 if self.token.type == ELEMENT_START: 281 if self.token.name_or_data in expected_tokens: 282 method = getattr(self, 'parse_' + self.token.name_or_data) 283 return method(name) 284 raise TokenMismatch(" or " .join(expected_tokens), self.token) 285 286 def parse_null(self, pname): 287 self.element_start('null') 288 self.element_end('null') 289 return Literal(None) 290 291 def parse_bool(self, pname): 292 self.element_start('bool') 293 value = int(self.character_data()) 294 self.element_end('bool') 295 return Literal(value) 296 297 def parse_int(self, pname): 298 self.element_start('int') 299 value = int(self.character_data()) 300 self.element_end('int') 301 return Literal(value) 302 303 def parse_uint(self, pname): 304 self.element_start('uint') 305 value = int(self.character_data()) 306 self.element_end('uint') 307 return Literal(value) 308 309 def parse_float(self, pname): 310 self.element_start('float') 311 value = float(self.character_data()) 312 self.element_end('float') 313 return Literal(value) 314 315 def parse_enum(self, pname): 316 self.element_start('enum') 317 name = self.character_data() 318 self.element_end('enum') 319 return NamedConstant(name) 320 321 def parse_string(self, pname): 322 self.element_start('string') 323 value = self.character_data() 324 self.element_end('string') 325 return Literal(value) 326 327 def parse_bytes(self, pname): 328 self.element_start('bytes') 329 value = self.character_data() 330 self.element_end('bytes') 331 return Blob(value) 332 333 def parse_array(self, pname): 334 self.element_start('array') 335 elems = [] 336 while self.token.type != ELEMENT_END: 337 elems.append(self.parse_elem('array')) 338 self.element_end('array') 339 return Array(elems) 340 341 def parse_elem(self, pname): 342 self.element_start('elem') 343 value = self.parse_value('elem') 344 self.element_end('elem') 345 return value 346 347 def parse_struct(self, pname): 348 attrs = self.element_start('struct') 349 name = attrs['name'] 350 members = [] 351 while self.token.type != ELEMENT_END: 352 members.append(self.parse_member(name)) 353 self.element_end('struct') 354 return Struct(name, members) 355 356 def parse_member(self, pname): 357 attrs = self.element_start('member') 358 name = attrs['name'] 359 value = self.parse_value(name) 360 self.element_end('member') 361 362 return name, value 363 364 def parse_ptr(self, pname): 365 self.element_start('ptr') 366 address = self.character_data() 367 self.element_end('ptr') 368 369 return Pointer(self.state, address, pname) 370 371 def handle_call(self, call): 372 pass 373 374 375class SimpleTraceDumper(TraceParser): 376 377 def __init__(self, fp, options, formatter, state): 378 TraceParser.__init__(self, fp, options, state) 379 self.options = options 380 self.formatter = formatter 381 self.pretty_printer = PrettyPrinter(self.formatter, options) 382 383 def handle_call(self, call): 384 if self.options.ignore_junk and call.is_junk: 385 return 386 387 call.visit(self.pretty_printer) 388 389 390class TraceDumper(SimpleTraceDumper): 391 392 def __init__(self, fp, options, formatter, state): 393 SimpleTraceDumper.__init__(self, fp, options, formatter, state) 394 self.call_stack = [] 395 396 def handle_call(self, call): 397 if self.options.ignore_junk and call.is_junk: 398 return 399 400 if self.options.named_ptrs: 401 self.call_stack.append(call) 402 else: 403 call.visit(self.pretty_printer) 404 405 406class ParseOptions(ModelOptions): 407 408 def __init__(self, args=None): 409 # Initialize options local to this module 410 self.plain = False 411 self.ignore_junk = False 412 413 ModelOptions.__init__(self, args) 414 415 416class Main: 417 '''Common main class for all retrace command line utilities.''' 418 419 def __init__(self): 420 pass 421 422 def main(self): 423 optparser = self.get_optparser() 424 args = optparser.parse_args() 425 options = self.make_options(args) 426 427 for fname in args.filename: 428 try: 429 if fname.endswith('.gz'): 430 from gzip import GzipFile 431 stream = io.TextIOWrapper(GzipFile(fname, 'rb')) 432 elif fname.endswith('.bz2'): 433 from bz2 import BZ2File 434 stream = io.TextIOWrapper(BZ2File(fname, 'rb')) 435 else: 436 stream = open(fname, 'rt') 437 except Exception as e: 438 print("ERROR: {}".format(str(e))) 439 sys.exit(1) 440 441 self.process_arg(stream, options) 442 443 def make_options(self, args): 444 return ParseOptions(args) 445 446 def get_optparser(self): 447 estr = "\nList of junk calls:\n" 448 for klass, call in sorted(trace_ignore_calls): 449 estr += f" {klass}::{call}\n" 450 451 optparser = argparse.ArgumentParser( 452 description="Parse and dump Gallium trace(s)", 453 formatter_class=argparse.RawDescriptionHelpFormatter, 454 epilog=estr) 455 456 optparser.add_argument("filename", action="extend", nargs="+", 457 type=str, metavar="filename", help="Gallium trace filename (plain or .gz, .bz2)") 458 459 optparser.add_argument("-p", "--plain", 460 action="store_const", const=True, default=False, 461 dest="plain", help="disable ANSI color etc. formatting") 462 463 optparser.add_argument("-S", "--suppress", 464 action="store_const", const=True, default=False, 465 dest="suppress_variants", help="suppress some variants in output for better diffability") 466 467 optparser.add_argument("-N", "--named", 468 action="store_const", const=True, default=False, 469 dest="named_ptrs", help="generate symbolic names for raw pointer values") 470 471 optparser.add_argument("-M", "--method-only", 472 action="store_const", const=True, default=False, 473 dest="method_only", help="output only call names without arguments") 474 475 optparser.add_argument("-I", "--ignore-junk", 476 action="store_const", const=True, default=False, 477 dest="ignore_junk", help="filter out/ignore junk calls (see below)") 478 479 return optparser 480 481 def process_arg(self, stream, options): 482 if options.plain: 483 formatter = format.Formatter(sys.stdout) 484 else: 485 formatter = format.DefaultFormatter(sys.stdout) 486 487 dump = TraceDumper(stream, options, formatter, TraceStateData()) 488 dump.parse() 489 490 if options.named_ptrs: 491 for call in dump.call_stack: 492 call.visit(dump.pretty_printer) 493 494 495if __name__ == '__main__': 496 Main().main() 497