blob: 5611814ddbaa7a7ed2b8392338eaca812c3e60da [file] [log] [blame]
#!/usr/bin/env python3
##########################################################################
#
# Copyright 2008 VMware, Inc.
# All Rights Reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sub license, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice (including the
# next paragraph) shall be included in all copies or substantial portions
# of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
# IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR
# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
##########################################################################
import io
import sys
import xml.parsers.expat as xpat
import argparse
import format
from model import *
trace_ignore_calls = set((
("pipe_screen", "is_format_supported"),
("pipe_screen", "get_name"),
("pipe_screen", "get_vendor"),
("pipe_screen", "get_param"),
("pipe_screen", "get_paramf"),
("pipe_screen", "get_shader_param"),
("pipe_screen", "get_compute_param"),
("pipe_screen", "get_disk_shader_cache"),
))
def trace_call_ignore(call):
return (call.klass, call.method) in trace_ignore_calls
ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF = range(4)
class XmlToken:
def __init__(self, type, name_or_data, attrs = None, line = None, column = None):
assert type in (ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF)
self.type = type
self.name_or_data = name_or_data
self.attrs = attrs
self.line = line
self.column = column
def __str__(self):
if self.type == ELEMENT_START:
return '<' + self.name_or_data + ' ...>'
if self.type == ELEMENT_END:
return '</' + self.name_or_data + '>'
if self.type == CHARACTER_DATA:
return self.name_or_data
if self.type == EOF:
return 'end of file'
assert 0
class XmlTokenizer:
"""Expat based XML tokenizer."""
def __init__(self, fp, skip_ws = True):
self.fp = fp
self.tokens = []
self.index = 0
self.final = False
self.skip_ws = skip_ws
self.character_pos = 0, 0
self.character_data = []
self.parser = xpat.ParserCreate()
self.parser.StartElementHandler = self.handle_element_start
self.parser.EndElementHandler = self.handle_element_end
self.parser.CharacterDataHandler = self.handle_character_data
def handle_element_start(self, name, attributes):
self.finish_character_data()
line, column = self.pos()
token = XmlToken(ELEMENT_START, name, attributes, line, column)
self.tokens.append(token)
def handle_element_end(self, name):
self.finish_character_data()
line, column = self.pos()
token = XmlToken(ELEMENT_END, name, None, line, column)
self.tokens.append(token)
def handle_character_data(self, data):
if not self.character_data:
self.character_pos = self.pos()
self.character_data.append(data)
def finish_character_data(self):
if self.character_data:
character_data = ''.join(self.character_data)
if not self.skip_ws or not character_data.isspace():
line, column = self.character_pos
token = XmlToken(CHARACTER_DATA, character_data, None, line, column)
self.tokens.append(token)
self.character_data = []
def next(self):
size = 16*1024
while self.index >= len(self.tokens) and not self.final:
self.tokens = []
self.index = 0
data = self.fp.read(size)
self.final = len(data) < size
data = data.rstrip('\0')
try:
self.parser.Parse(data, self.final)
except xpat.ExpatError as e:
#if e.code == xpat.errors.XML_ERROR_NO_ELEMENTS:
if e.code == 3:
pass
else:
raise e
if self.index >= len(self.tokens):
line, column = self.pos()
token = XmlToken(EOF, None, None, line, column)
else:
token = self.tokens[self.index]
self.index += 1
return token
def pos(self):
return self.parser.CurrentLineNumber, self.parser.CurrentColumnNumber
class TokenMismatch(Exception):
def __init__(self, expected, found):
self.expected = expected
self.found = found
def __str__(self):
return '%u:%u: %s expected, %s found' % (self.found.line, self.found.column, str(self.expected), str(self.found))
class XmlParser:
"""Base XML document parser."""
def __init__(self, fp):
self.tokenizer = XmlTokenizer(fp)
self.consume()
def consume(self):
self.token = self.tokenizer.next()
def match_element_start(self, name):
return self.token.type == ELEMENT_START and self.token.name_or_data == name
def match_element_end(self, name):
return self.token.type == ELEMENT_END and self.token.name_or_data == name
def element_start(self, name):
while self.token.type == CHARACTER_DATA:
self.consume()
if self.token.type != ELEMENT_START:
raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token)
if self.token.name_or_data != name:
raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token)
attrs = self.token.attrs
self.consume()
return attrs
def element_end(self, name):
while self.token.type == CHARACTER_DATA:
self.consume()
if self.token.type != ELEMENT_END:
raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token)
if self.token.name_or_data != name:
raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token)
self.consume()
def character_data(self, strip = True):
data = ''
while self.token.type == CHARACTER_DATA:
data += self.token.name_or_data
self.consume()
if strip:
data = data.strip()
return data
class TraceParser(XmlParser):
def __init__(self, fp, options, state):
XmlParser.__init__(self, fp)
self.last_call_no = 0
self.state = state
self.options = options
def parse(self):
self.element_start('trace')
while self.token.type not in (ELEMENT_END, EOF):
call = self.parse_call()
call.is_junk = trace_call_ignore(call)
self.handle_call(call)
if self.token.type != EOF:
self.element_end('trace')
def parse_call(self):
attrs = self.element_start('call')
try:
no = int(attrs['no'])
except KeyError as e:
self.last_call_no += 1
no = self.last_call_no
else:
self.last_call_no = no
klass = attrs['class']
method = attrs['method']
args = []
ret = None
time = None
while self.token.type == ELEMENT_START:
if self.token.name_or_data == 'arg':
arg = self.parse_arg()
args.append(arg)
elif self.token.name_or_data == 'ret':
ret = self.parse_ret()
elif self.token.name_or_data == 'call':
# ignore nested function calls
self.parse_call()
elif self.token.name_or_data == 'time':
time = self.parse_time()
else:
raise TokenMismatch("<arg ...> or <ret ...>", self.token)
self.element_end('call')
return Call(no, klass, method, args, ret, time)
def parse_arg(self):
attrs = self.element_start('arg')
name = attrs['name']
value = self.parse_value(name)
self.element_end('arg')
return name, value
def parse_ret(self):
attrs = self.element_start('ret')
value = self.parse_value('ret')
self.element_end('ret')
return value
def parse_time(self):
attrs = self.element_start('time')
time = self.parse_value('time');
self.element_end('time')
return time
def parse_value(self, name):
expected_tokens = ('null', 'bool', 'int', 'uint', 'float', 'string', 'enum', 'array', 'struct', 'ptr', 'bytes')
if self.token.type == ELEMENT_START:
if self.token.name_or_data in expected_tokens:
method = getattr(self, 'parse_' + self.token.name_or_data)
return method(name)
raise TokenMismatch(" or " .join(expected_tokens), self.token)
def parse_null(self, pname):
self.element_start('null')
self.element_end('null')
return Literal(None)
def parse_bool(self, pname):
self.element_start('bool')
value = int(self.character_data())
self.element_end('bool')
return Literal(value)
def parse_int(self, pname):
self.element_start('int')
value = int(self.character_data())
self.element_end('int')
return Literal(value)
def parse_uint(self, pname):
self.element_start('uint')
value = int(self.character_data())
self.element_end('uint')
return Literal(value)
def parse_float(self, pname):
self.element_start('float')
value = float(self.character_data())
self.element_end('float')
return Literal(value)
def parse_enum(self, pname):
self.element_start('enum')
name = self.character_data()
self.element_end('enum')
return NamedConstant(name)
def parse_string(self, pname):
self.element_start('string')
value = self.character_data()
self.element_end('string')
return Literal(value)
def parse_bytes(self, pname):
self.element_start('bytes')
value = self.character_data()
self.element_end('bytes')
return Blob(value)
def parse_array(self, pname):
self.element_start('array')
elems = []
while self.token.type != ELEMENT_END:
elems.append(self.parse_elem('array'))
self.element_end('array')
return Array(elems)
def parse_elem(self, pname):
self.element_start('elem')
value = self.parse_value('elem')
self.element_end('elem')
return value
def parse_struct(self, pname):
attrs = self.element_start('struct')
name = attrs['name']
members = []
while self.token.type != ELEMENT_END:
members.append(self.parse_member(name))
self.element_end('struct')
return Struct(name, members)
def parse_member(self, pname):
attrs = self.element_start('member')
name = attrs['name']
value = self.parse_value(name)
self.element_end('member')
return name, value
def parse_ptr(self, pname):
self.element_start('ptr')
address = self.character_data()
self.element_end('ptr')
return Pointer(self.state, address, pname)
def handle_call(self, call):
pass
class SimpleTraceDumper(TraceParser):
def __init__(self, fp, options, formatter, state):
TraceParser.__init__(self, fp, options, state)
self.options = options
self.formatter = formatter
self.pretty_printer = PrettyPrinter(self.formatter, options)
def handle_call(self, call):
if self.options.ignore_junk and call.is_junk:
return
call.visit(self.pretty_printer)
class TraceDumper(SimpleTraceDumper):
def __init__(self, fp, options, formatter, state):
SimpleTraceDumper.__init__(self, fp, options, formatter, state)
self.call_stack = []
def handle_call(self, call):
if self.options.ignore_junk and call.is_junk:
return
if self.options.named_ptrs:
self.call_stack.append(call)
else:
call.visit(self.pretty_printer)
class ParseOptions(ModelOptions):
def __init__(self, args=None):
# Initialize options local to this module
self.plain = False
self.ignore_junk = False
ModelOptions.__init__(self, args)
class Main:
'''Common main class for all retrace command line utilities.'''
def __init__(self):
pass
def main(self):
optparser = self.get_optparser()
args = optparser.parse_args()
options = self.make_options(args)
for fname in args.filename:
try:
if fname.endswith('.gz'):
from gzip import GzipFile
stream = io.TextIOWrapper(GzipFile(fname, 'rb'))
elif fname.endswith('.bz2'):
from bz2 import BZ2File
stream = io.TextIOWrapper(BZ2File(fname, 'rb'))
else:
stream = open(fname, 'rt')
except Exception as e:
print("ERROR: {}".format(str(e)))
sys.exit(1)
self.process_arg(stream, options)
def make_options(self, args):
return ParseOptions(args)
def get_optparser(self):
estr = "\nList of junk calls:\n"
for klass, call in sorted(trace_ignore_calls):
estr += f" {klass}::{call}\n"
optparser = argparse.ArgumentParser(
description="Parse and dump Gallium trace(s)",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=estr)
optparser.add_argument("filename", action="extend", nargs="+",
type=str, metavar="filename", help="Gallium trace filename (plain or .gz, .bz2)")
optparser.add_argument("-p", "--plain",
action="store_const", const=True, default=False,
dest="plain", help="disable ANSI color etc. formatting")
optparser.add_argument("-S", "--suppress",
action="store_const", const=True, default=False,
dest="suppress_variants", help="suppress some variants in output for better diffability")
optparser.add_argument("-N", "--named",
action="store_const", const=True, default=False,
dest="named_ptrs", help="generate symbolic names for raw pointer values")
optparser.add_argument("-M", "--method-only",
action="store_const", const=True, default=False,
dest="method_only", help="output only call names without arguments")
optparser.add_argument("-I", "--ignore-junk",
action="store_const", const=True, default=False,
dest="ignore_junk", help="filter out/ignore junk calls (see below)")
return optparser
def process_arg(self, stream, options):
if options.plain:
formatter = format.Formatter(sys.stdout)
else:
formatter = format.DefaultFormatter(sys.stdout)
dump = TraceDumper(stream, options, formatter, TraceStateData())
dump.parse()
if options.named_ptrs:
for call in dump.call_stack:
call.visit(dump.pretty_printer)
if __name__ == '__main__':
Main().main()