mirror of
https://gitlab.freedesktop.org/mesa/mesa.git
synced 2026-05-20 11:18:11 +02:00
The limitations of current approach for Gallium trace diffing via 'tracediff.sh' are becoming all too apparent, as we are first dumping both trace to text and performing plain line-based sdiff on them. This obviously loses the context of calls and sometimes results in confusing diffs when subsequent calls are similar enough. It also prevents us from formatting the diff output in ways that would benefit readability. In attempt to rectify the situation, reimplement the diffing completely in Python, using difflib and adding the necessary plumbing into the trace model objects etc. Signed-off-by: Matti Hamalainen <ccr@tnsp.org> Acked-by: Mike Blumenkrantz <michael.blumenkrantz@gmail.com> Reviewed-by: Dylan Baker <dylan@pnwbakers.com> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/17135>
340 lines
9 KiB
Python
Executable file
340 lines
9 KiB
Python
Executable file
#!/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.
|
|
#
|
|
##########################################################################
|
|
|
|
|
|
'''Trace data model.'''
|
|
|
|
|
|
import sys
|
|
import string
|
|
import binascii
|
|
from io import StringIO
|
|
import format
|
|
|
|
|
|
class ModelOptions:
|
|
|
|
def __init__(self, args=None):
|
|
# Initialize the options we need to exist,
|
|
# with some reasonable defaults
|
|
self.plain = False
|
|
self.suppress_variants = False
|
|
self.named_ptrs = False
|
|
self.method_only = False
|
|
|
|
# If args is specified, we assume it is the result object
|
|
# from ArgumentParser.parse_args(). Copy the attribute values
|
|
# we have from it, if they exist.
|
|
if args is not None:
|
|
for var in self.__dict__:
|
|
if var in args.__dict__:
|
|
self.__dict__[var] = args.__dict__[var]
|
|
|
|
|
|
class TraceStateData:
|
|
|
|
def __init__(self):
|
|
self.ptr_list = {}
|
|
self.ptr_type_list = {}
|
|
self.ptr_types_list = {}
|
|
|
|
|
|
class Node:
|
|
|
|
def visit(self, visitor):
|
|
raise NotImplementedError
|
|
|
|
def __str__(self):
|
|
stream = StringIO()
|
|
formatter = format.Formatter(stream)
|
|
pretty_printer = PrettyPrinter(formatter, {})
|
|
self.visit(pretty_printer)
|
|
return stream.getvalue()
|
|
|
|
def __hash__(self):
|
|
raise NotImplementedError
|
|
|
|
|
|
class Literal(Node):
|
|
|
|
def __init__(self, value):
|
|
self.value = value
|
|
|
|
def visit(self, visitor):
|
|
visitor.visit_literal(self)
|
|
|
|
def __hash__(self):
|
|
return hash(self.value)
|
|
|
|
|
|
class Blob(Node):
|
|
|
|
def __init__(self, value):
|
|
self.value = binascii.a2b_hex(value)
|
|
|
|
def getValue(self):
|
|
return self.value
|
|
|
|
def visit(self, visitor):
|
|
visitor.visit_blob(self)
|
|
|
|
def __hash__(self):
|
|
return hash(self.value)
|
|
|
|
|
|
class NamedConstant(Node):
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
def visit(self, visitor):
|
|
visitor.visit_named_constant(self)
|
|
|
|
def __hash__(self):
|
|
return hash(self.name)
|
|
|
|
|
|
class Array(Node):
|
|
|
|
def __init__(self, elements):
|
|
self.elements = elements
|
|
|
|
def visit(self, visitor):
|
|
visitor.visit_array(self)
|
|
|
|
def __hash__(self):
|
|
tmp = 0
|
|
for mobj in self.elements:
|
|
tmp = tmp ^ hash(mobj)
|
|
return tmp
|
|
|
|
|
|
class Struct(Node):
|
|
|
|
def __init__(self, name, members):
|
|
self.name = name
|
|
self.members = members
|
|
|
|
def visit(self, visitor):
|
|
visitor.visit_struct(self)
|
|
|
|
def __hash__(self):
|
|
tmp = hash(self.name)
|
|
for mname, mobj in self.members:
|
|
tmp = tmp ^ hash(mname) ^ hash(mobj)
|
|
return tmp
|
|
|
|
|
|
class Pointer(Node):
|
|
|
|
ptr_ignore_list = ["ret", "elem"]
|
|
|
|
def __init__(self, state, address, pname):
|
|
self.address = address
|
|
self.state = state
|
|
|
|
# Check if address exists in list and if it is a return value address
|
|
t1 = address in state.ptr_list
|
|
if t1:
|
|
rname = state.ptr_type_list[address]
|
|
t2 = rname in self.ptr_ignore_list and pname not in self.ptr_ignore_list
|
|
else:
|
|
rname = pname
|
|
t2 = False
|
|
|
|
# If address does NOT exist (add it), OR IS a ret value (update with new type)
|
|
if not t1 or t2:
|
|
# If previously set to ret value, remove one from count
|
|
if t1 and t2:
|
|
self.adjust_ptr_type_count(rname, -1)
|
|
|
|
# Add / update
|
|
self.adjust_ptr_type_count(pname, 1)
|
|
tmp = "{}_{}".format(pname, state.ptr_types_list[pname])
|
|
state.ptr_list[address] = tmp
|
|
state.ptr_type_list[address] = pname
|
|
|
|
def adjust_ptr_type_count(self, pname, delta):
|
|
if pname not in self.state.ptr_types_list:
|
|
self.state.ptr_types_list[pname] = 0
|
|
|
|
self.state.ptr_types_list[pname] += delta
|
|
|
|
def named_address(self):
|
|
return self.state.ptr_list[self.address]
|
|
|
|
def visit(self, visitor):
|
|
visitor.visit_pointer(self)
|
|
|
|
def __hash__(self):
|
|
return hash(self.named_address())
|
|
|
|
|
|
class Call:
|
|
|
|
def __init__(self, no, klass, method, args, ret, time):
|
|
self.no = no
|
|
self.klass = klass
|
|
self.method = method
|
|
self.args = args
|
|
self.ret = ret
|
|
self.time = time
|
|
|
|
# Calculate hashvalue "cached" into a variable
|
|
self.hashvalue = hash(self.klass) ^ hash(self.method)
|
|
for mname, mobj in self.args:
|
|
self.hashvalue = self.hashvalue ^ hash(mname) ^ hash(mobj)
|
|
|
|
def visit(self, visitor):
|
|
visitor.visit_call(self)
|
|
|
|
def __hash__(self):
|
|
return self.hashvalue
|
|
|
|
def __eq__(self, other):
|
|
return self.hashvalue == other.hashvalue
|
|
|
|
|
|
class Trace:
|
|
|
|
def __init__(self, calls):
|
|
self.calls = calls
|
|
|
|
def visit(self, visitor):
|
|
visitor.visit_trace(self)
|
|
|
|
|
|
class Visitor:
|
|
|
|
def visit_literal(self, node):
|
|
raise NotImplementedError
|
|
|
|
def visit_blob(self, node):
|
|
raise NotImplementedError
|
|
|
|
def visit_named_constant(self, node):
|
|
raise NotImplementedError
|
|
|
|
def visit_array(self, node):
|
|
raise NotImplementedError
|
|
|
|
def visit_struct(self, node):
|
|
raise NotImplementedError
|
|
|
|
def visit_pointer(self, node):
|
|
raise NotImplementedError
|
|
|
|
def visit_call(self, node):
|
|
raise NotImplementedError
|
|
|
|
def visit_trace(self, node):
|
|
raise NotImplementedError
|
|
|
|
|
|
class PrettyPrinter:
|
|
|
|
def __init__(self, formatter, options):
|
|
self.formatter = formatter
|
|
self.options = options
|
|
|
|
def visit_literal(self, node):
|
|
if node.value is None:
|
|
self.formatter.literal('NULL')
|
|
return
|
|
|
|
if isinstance(node.value, str):
|
|
self.formatter.literal('"' + node.value + '"')
|
|
return
|
|
|
|
self.formatter.literal(repr(node.value))
|
|
|
|
def visit_blob(self, node):
|
|
self.formatter.address('blob()')
|
|
|
|
def visit_named_constant(self, node):
|
|
self.formatter.literal(node.name)
|
|
|
|
def visit_array(self, node):
|
|
self.formatter.text('{')
|
|
sep = ''
|
|
for value in node.elements:
|
|
self.formatter.text(sep)
|
|
value.visit(self)
|
|
sep = ', '
|
|
self.formatter.text('}')
|
|
|
|
def visit_struct(self, node):
|
|
self.formatter.text('{')
|
|
sep = ''
|
|
for name, value in node.members:
|
|
self.formatter.text(sep)
|
|
self.formatter.variable(name)
|
|
self.formatter.text(' = ')
|
|
value.visit(self)
|
|
sep = ', '
|
|
self.formatter.text('}')
|
|
|
|
def visit_pointer(self, node):
|
|
if self.options.named_ptrs:
|
|
self.formatter.address(node.named_address())
|
|
else:
|
|
self.formatter.address(node.address)
|
|
|
|
def visit_call(self, node):
|
|
if not self.options.suppress_variants:
|
|
self.formatter.text(f'{node.no} ')
|
|
|
|
if node.klass is not None:
|
|
self.formatter.function(node.klass + '::' + node.method)
|
|
else:
|
|
self.formatter.function(node.method)
|
|
|
|
if not self.options.method_only:
|
|
self.formatter.text('(')
|
|
sep = ''
|
|
for name, value in node.args:
|
|
self.formatter.text(sep)
|
|
self.formatter.variable(name)
|
|
self.formatter.text(' = ')
|
|
value.visit(self)
|
|
sep = ', '
|
|
self.formatter.text(')')
|
|
if node.ret is not None:
|
|
self.formatter.text(' = ')
|
|
node.ret.visit(self)
|
|
|
|
if not self.options.suppress_variants and node.time is not None:
|
|
self.formatter.text(' // time ')
|
|
node.time.visit(self)
|
|
|
|
self.formatter.newline()
|
|
|
|
def visit_trace(self, node):
|
|
for call in node.calls:
|
|
call.visit(self)
|
|
|