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 39ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF = range(4) 40 41 42class XmlToken: 43 44 def __init__(self, type, name_or_data, attrs = None, line = None, column = None): 45 assert type in (ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF) 46 self.type = type 47 self.name_or_data = name_or_data 48 self.attrs = attrs 49 self.line = line 50 self.column = column 51 52 def __str__(self): 53 if self.type == ELEMENT_START: 54 return '<' + self.name_or_data + ' ...>' 55 if self.type == ELEMENT_END: 56 return '</' + self.name_or_data + '>' 57 if self.type == CHARACTER_DATA: 58 return self.name_or_data 59 if self.type == EOF: 60 return 'end of file' 61 assert 0 62 63 64class XmlTokenizer: 65 """Expat based XML tokenizer.""" 66 67 def __init__(self, fp, skip_ws = True): 68 self.fp = fp 69 self.tokens = [] 70 self.index = 0 71 self.final = False 72 self.skip_ws = skip_ws 73 74 self.character_pos = 0, 0 75 self.character_data = '' 76 77 self.parser = xpat.ParserCreate() 78 self.parser.StartElementHandler = self.handle_element_start 79 self.parser.EndElementHandler = self.handle_element_end 80 self.parser.CharacterDataHandler = self.handle_character_data 81 82 def handle_element_start(self, name, attributes): 83 self.finish_character_data() 84 line, column = self.pos() 85 token = XmlToken(ELEMENT_START, name, attributes, line, column) 86 self.tokens.append(token) 87 88 def handle_element_end(self, name): 89 self.finish_character_data() 90 line, column = self.pos() 91 token = XmlToken(ELEMENT_END, name, None, line, column) 92 self.tokens.append(token) 93 94 def handle_character_data(self, data): 95 if not self.character_data: 96 self.character_pos = self.pos() 97 self.character_data += data 98 99 def finish_character_data(self): 100 if self.character_data: 101 if not self.skip_ws or not self.character_data.isspace(): 102 line, column = self.character_pos 103 token = XmlToken(CHARACTER_DATA, self.character_data, None, line, column) 104 self.tokens.append(token) 105 self.character_data = '' 106 107 def next(self): 108 size = 16*1024 109 while self.index >= len(self.tokens) and not self.final: 110 self.tokens = [] 111 self.index = 0 112 data = self.fp.read(size) 113 self.final = len(data) < size 114 data = data.rstrip('\0') 115 try: 116 self.parser.Parse(data, self.final) 117 except xpat.ExpatError as e: 118 #if e.code == xpat.errors.XML_ERROR_NO_ELEMENTS: 119 if e.code == 3: 120 pass 121 else: 122 raise e 123 if self.index >= len(self.tokens): 124 line, column = self.pos() 125 token = XmlToken(EOF, None, None, line, column) 126 else: 127 token = self.tokens[self.index] 128 self.index += 1 129 return token 130 131 def pos(self): 132 return self.parser.CurrentLineNumber, self.parser.CurrentColumnNumber 133 134 135class TokenMismatch(Exception): 136 137 def __init__(self, expected, found): 138 self.expected = expected 139 self.found = found 140 141 def __str__(self): 142 return '%u:%u: %s expected, %s found' % (self.found.line, self.found.column, str(self.expected), str(self.found)) 143 144 145 146class XmlParser: 147 """Base XML document parser.""" 148 149 def __init__(self, fp): 150 self.tokenizer = XmlTokenizer(fp) 151 self.consume() 152 153 def consume(self): 154 self.token = self.tokenizer.next() 155 156 def match_element_start(self, name): 157 return self.token.type == ELEMENT_START and self.token.name_or_data == name 158 159 def match_element_end(self, name): 160 return self.token.type == ELEMENT_END and self.token.name_or_data == name 161 162 def element_start(self, name): 163 while self.token.type == CHARACTER_DATA: 164 self.consume() 165 if self.token.type != ELEMENT_START: 166 raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token) 167 if self.token.name_or_data != name: 168 raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token) 169 attrs = self.token.attrs 170 self.consume() 171 return attrs 172 173 def element_end(self, name): 174 while self.token.type == CHARACTER_DATA: 175 self.consume() 176 if self.token.type != ELEMENT_END: 177 raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token) 178 if self.token.name_or_data != name: 179 raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token) 180 self.consume() 181 182 def character_data(self, strip = True): 183 data = '' 184 while self.token.type == CHARACTER_DATA: 185 data += self.token.name_or_data 186 self.consume() 187 if strip: 188 data = data.strip() 189 return data 190 191 192class TraceParser(XmlParser): 193 194 def __init__(self, fp): 195 XmlParser.__init__(self, fp) 196 self.last_call_no = 0 197 198 def parse(self): 199 self.element_start('trace') 200 while self.token.type not in (ELEMENT_END, EOF): 201 call = self.parse_call() 202 self.handle_call(call) 203 if self.token.type != EOF: 204 self.element_end('trace') 205 206 def parse_call(self): 207 attrs = self.element_start('call') 208 try: 209 no = int(attrs['no']) 210 except KeyError as e: 211 self.last_call_no += 1 212 no = self.last_call_no 213 else: 214 self.last_call_no = no 215 klass = attrs['class'] 216 method = attrs['method'] 217 args = [] 218 ret = None 219 time = None 220 while self.token.type == ELEMENT_START: 221 if self.token.name_or_data == 'arg': 222 arg = self.parse_arg() 223 args.append(arg) 224 elif self.token.name_or_data == 'ret': 225 ret = self.parse_ret() 226 elif self.token.name_or_data == 'call': 227 # ignore nested function calls 228 self.parse_call() 229 elif self.token.name_or_data == 'time': 230 time = self.parse_time() 231 else: 232 raise TokenMismatch("<arg ...> or <ret ...>", self.token) 233 self.element_end('call') 234 235 return Call(no, klass, method, args, ret, time) 236 237 def parse_arg(self): 238 attrs = self.element_start('arg') 239 name = attrs['name'] 240 value = self.parse_value(name) 241 self.element_end('arg') 242 243 return name, value 244 245 def parse_ret(self): 246 attrs = self.element_start('ret') 247 value = self.parse_value('ret') 248 self.element_end('ret') 249 250 return value 251 252 def parse_time(self): 253 attrs = self.element_start('time') 254 time = self.parse_value('time'); 255 self.element_end('time') 256 return time 257 258 def parse_value(self, name): 259 expected_tokens = ('null', 'bool', 'int', 'uint', 'float', 'string', 'enum', 'array', 'struct', 'ptr', 'bytes') 260 if self.token.type == ELEMENT_START: 261 if self.token.name_or_data in expected_tokens: 262 method = getattr(self, 'parse_' + self.token.name_or_data) 263 return method(name) 264 raise TokenMismatch(" or " .join(expected_tokens), self.token) 265 266 def parse_null(self, pname): 267 self.element_start('null') 268 self.element_end('null') 269 return Literal(None) 270 271 def parse_bool(self, pname): 272 self.element_start('bool') 273 value = int(self.character_data()) 274 self.element_end('bool') 275 return Literal(value) 276 277 def parse_int(self, pname): 278 self.element_start('int') 279 value = int(self.character_data()) 280 self.element_end('int') 281 return Literal(value) 282 283 def parse_uint(self, pname): 284 self.element_start('uint') 285 value = int(self.character_data()) 286 self.element_end('uint') 287 return Literal(value) 288 289 def parse_float(self, pname): 290 self.element_start('float') 291 value = float(self.character_data()) 292 self.element_end('float') 293 return Literal(value) 294 295 def parse_enum(self, pname): 296 self.element_start('enum') 297 name = self.character_data() 298 self.element_end('enum') 299 return NamedConstant(name) 300 301 def parse_string(self, pname): 302 self.element_start('string') 303 value = self.character_data() 304 self.element_end('string') 305 return Literal(value) 306 307 def parse_bytes(self, pname): 308 self.element_start('bytes') 309 value = self.character_data() 310 self.element_end('bytes') 311 return Blob(value) 312 313 def parse_array(self, pname): 314 self.element_start('array') 315 elems = [] 316 while self.token.type != ELEMENT_END: 317 elems.append(self.parse_elem('array')) 318 self.element_end('array') 319 return Array(elems) 320 321 def parse_elem(self, pname): 322 self.element_start('elem') 323 value = self.parse_value('elem') 324 self.element_end('elem') 325 return value 326 327 def parse_struct(self, pname): 328 attrs = self.element_start('struct') 329 name = attrs['name'] 330 members = [] 331 while self.token.type != ELEMENT_END: 332 members.append(self.parse_member(name)) 333 self.element_end('struct') 334 return Struct(name, members) 335 336 def parse_member(self, pname): 337 attrs = self.element_start('member') 338 name = attrs['name'] 339 value = self.parse_value(name) 340 self.element_end('member') 341 342 return name, value 343 344 def parse_ptr(self, pname): 345 self.element_start('ptr') 346 address = self.character_data() 347 self.element_end('ptr') 348 349 return Pointer(address, pname) 350 351 def handle_call(self, call): 352 pass 353 354 355class SimpleTraceDumper(TraceParser): 356 357 def __init__(self, fp, options, formatter): 358 TraceParser.__init__(self, fp) 359 self.options = options 360 self.formatter = formatter 361 self.pretty_printer = PrettyPrinter(self.formatter, options) 362 363 def handle_call(self, call): 364 call.visit(self.pretty_printer) 365 self.formatter.newline() 366 367 368class TraceDumper(SimpleTraceDumper): 369 370 def __init__(self, fp, options, formatter): 371 SimpleTraceDumper.__init__(self, fp, options, formatter) 372 self.call_stack = [] 373 374 def handle_call(self, call): 375 if self.options.named_ptrs: 376 self.call_stack.append(call) 377 else: 378 call.visit(self.pretty_printer) 379 self.formatter.newline() 380 381 def dump_calls(self): 382 for call in self.call_stack: 383 call.visit(self.pretty_printer) 384 self.formatter.newline() 385 386 387class Main: 388 '''Common main class for all retrace command line utilities.''' 389 390 def __init__(self): 391 pass 392 393 def main(self): 394 optparser = self.get_optparser() 395 args = optparser.parse_args() 396 397 for fname in args.filename: 398 try: 399 if fname.endswith('.gz'): 400 from gzip import GzipFile 401 stream = io.TextIOWrapper(GzipFile(fname, 'rb')) 402 elif fname.endswith('.bz2'): 403 from bz2 import BZ2File 404 stream = io.TextIOWrapper(BZ2File(fname, 'rb')) 405 else: 406 stream = open(fname, 'rt') 407 except Exception as e: 408 print("ERROR: {}".format(str(e))) 409 sys.exit(1) 410 411 self.process_arg(stream, args) 412 413 def get_optparser(self): 414 optparser = argparse.ArgumentParser( 415 description="Parse and dump Gallium trace(s)") 416 optparser.add_argument("filename", action="extend", nargs="+", 417 type=str, metavar="filename", help="Gallium trace filename (plain or .gz, .bz2)") 418 optparser.add_argument("-p", "--plain", 419 action="store_const", const=True, default=False, 420 dest="plain", help="disable ANSI color etc. formatting") 421 optparser.add_argument("-S", "--suppress", 422 action="store_const", const=True, default=False, 423 dest="suppress_variants", help="suppress some variants in output for better diffability") 424 optparser.add_argument("-N", "--named", 425 action="store_const", const=True, default=False, 426 dest="named_ptrs", help="generate symbolic names for raw pointer values") 427 optparser.add_argument("-M", "--method-only", 428 action="store_const", const=True, default=False, 429 dest="method_only", help="output only call names without arguments") 430 431 return optparser 432 433 def process_arg(self, stream, options): 434 if options.plain: 435 formatter = format.Formatter(sys.stdout) 436 else: 437 formatter = format.DefaultFormatter(sys.stdout) 438 439 parser = TraceDumper(stream, options, formatter) 440 parser.parse() 441 442 if options.named_ptrs: 443 parser.dump_calls() 444 445 446if __name__ == '__main__': 447 Main().main() 448