]> git.cworth.org Git - apitrace/blobdiff - scripts/tracediff2.py
tracediff2: Handle variations in number of arguments.
[apitrace] / scripts / tracediff2.py
index 7dc11e2bf1c48caa8729640c00afc490290c0e2d..0f59d87983df51be1a23bb8400c3c601d172402b 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 ##########################################################################
 #
-# Copyright 2011 Jose Fonseca
+# Copyright 2011-2012 Jose Fonseca
 # All Rights Reserved.
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
 
 
 import difflib
+import itertools
 import optparse
 import os.path
 import subprocess
 import sys
 
-from unpickle import Unpickler
-from highlight import Highlighter
+from unpickle import Unpickler, Dumper, Rebuilder
+from highlight import ColorHighlighter, LessHighlighter
 
 
 ignoredFunctionNames = set([
     'glGetString',
     'glXGetClientString',
     'glXGetCurrentDisplay',
+    'glXGetCurrentContext',
     'glXGetProcAddress',
     'glXGetProcAddressARB',
     'wglGetProcAddress',
 ])
 
 
+class Blob:
+    '''Data-less proxy for bytearrays, to save memory.'''
+
+    def __init__(self, size, hash):
+        self.size = size
+        self.hash = hash
+
+    def __repr__(self):
+        return 'blob(%u)' % self.size
+
+    def __eq__(self, other):
+        return self.size == other.size and self.hash == other.hash
+
+    def __hash__(self):
+        return self.hash
+
+
+class BlobReplacer(Rebuilder):
+    '''Replace blobs with proxys.'''
+
+    def visitByteArray(self, obj):
+        return Blob(len(obj), hash(str(obj)))
+
+    def visitCall(self, call):
+        call.args = map(self.visit, call.args)
+        call.ret = self.visit(call.ret)
+
+
 class Loader(Unpickler):
 
     def __init__(self, stream):
         Unpickler.__init__(self, stream)
         self.calls = []
+        self.rebuilder = BlobReplacer()
 
     def handleCall(self, call):
-        call.no = None
-        hash(call)
         if call.functionName not in ignoredFunctionNames:
+            self.rebuilder.visitCall(call)
             self.calls.append(call)
 
 
-def readtrace(trace):
+def readtrace(trace, calls):
     p = subprocess.Popen(
         args = [
             options.apitrace,
             'pickle',
-            '--calls=' + options.calls,
+            '--symbolic',
+            '--calls=' + calls,
             trace
         ],
         stdout = subprocess.PIPE,
     )
 
-    calls = []
     parser = Loader(p.stdout)
     parser.parse()
     return parser.calls
@@ -77,27 +107,34 @@ def readtrace(trace):
 
 class SDiffer:
 
-    def __init__(self, a, b, highlighter):
+    def __init__(self, a, b, highlighter, callNos = False):
         self.a = a
         self.b = b
         self.highlighter = highlighter
         self.delete_color = highlighter.red
         self.insert_color = highlighter.green
+        self.callNos = callNos
+        self.aSpace = 0
+        self.bSpace = 0
+        self.dumper = Dumper()
 
     def diff(self):
-        matcher = difflib.SequenceMatcher(None, self.a, self.b)
+        matcher = difflib.SequenceMatcher(self.isjunk, self.a, self.b)
         for tag, alo, ahi, blo, bhi in matcher.get_opcodes():
             if tag == 'replace':
                 self.replace(alo, ahi, blo, bhi)
             elif tag == 'delete':
-                self.delete(alo, ahi)
+                self.delete(alo, ahi, blo, bhi)
             elif tag == 'insert':
-                self.insert(blo, bhi)
+                self.insert(alo, ahi, blo, bhi)
             elif tag == 'equal':
-                self.equal(alo, ahi)
+                self.equal(alo, ahi, blo, bhi)
             else:
                 raise ValueError, 'unknown tag %s' % (tag,)
 
+    def isjunk(self, call):
+        return call.functionName == 'glGetError' and call.ret in ('GL_NO_ERROR', 0)
+
     def replace(self, alo, ahi, blo, bhi):
         assert alo < ahi and blo < bhi
         
@@ -113,9 +150,9 @@ class SDiffer:
             if tag == 'replace':
                 self.replace_dissimilar(_alo, _ahi, _blo, _bhi)
             elif tag == 'delete':
-                self.delete(_alo, _ahi)
+                self.delete(_alo, _ahi, _blo, _bhi)
             elif tag == 'insert':
-                self.insert(_blo, _bhi)
+                self.insert(_alo, _ahi, _blo, _bhi)
             elif tag == 'equal':
                 self.replace_similar(_alo, _ahi, _blo, _bhi)
             else:
@@ -125,15 +162,28 @@ class SDiffer:
         assert alo < ahi and blo < bhi
         assert ahi - alo == bhi - blo
         for i in xrange(0, bhi - blo):
+            self.highlighter.write('| ')
             a_call = self.a[alo + i]
             b_call = self.b[blo + i]
             assert a_call.functionName == b_call.functionName
-            assert len(a_call.args) == len(b_call.args)
-            self.highlighter.write('  ' + b_call.functionName + '(')
+            self.dumpCallNos(a_call.no, b_call.no)
+            self.highlighter.bold(True)
+            self.highlighter.write(b_call.functionName)
+            self.highlighter.bold(False)
+            self.highlighter.write('(')
             sep = ''
-            for j in xrange(len(b_call.args)):
+            numArgs = max(len(a_call.args), len(b_call.args))
+            for j in xrange(numArgs):
                 self.highlighter.write(sep)
-                self.replace_value(a_call.args[j], b_call.args[j])
+                try:
+                    a_arg = a_call.args[j]
+                except IndexError:
+                    pass
+                try:
+                    b_arg = b_call.args[j]
+                except IndexError:
+                    pass
+                self.replace_value(a_arg, b_arg)
                 sep = ', '
             self.highlighter.write(')')
             if a_call.ret is not None or b_call.ret is not None:
@@ -144,59 +194,93 @@ class SDiffer:
     def replace_dissimilar(self, alo, ahi, blo, bhi):
         assert alo < ahi and blo < bhi
         if bhi - blo < ahi - alo:
-            first  = self.insert(blo, bhi)
-            second = self.delete(alo, ahi)
+            self.insert(alo, alo, blo, bhi)
+            self.delete(alo, ahi, bhi, bhi)
         else:
-            first  = self.delete(alo, ahi)
-            second = self.insert(blo, bhi)
-
-        for g in first, second:
-            for line in g:
-                yield line
+            self.delete(alo, ahi, blo, blo)
+            self.insert(ahi, ahi, blo, bhi)
 
     def replace_value(self, a, b):
         if b == a:
-            self.highlighter.write(str(b))
+            self.highlighter.write(self.dumper.visit(b))
         else:
+            self.highlighter.strike()
             self.highlighter.color(self.delete_color)
-            self.highlighter.write(str(b))
+            self.highlighter.write(self.dumper.visit(a))
             self.highlighter.normal()
-            self.highlighter.write(" -> ")
+            self.highlighter.write(" ")
             self.highlighter.color(self.insert_color)
-            self.highlighter.write(str(b))
+            self.highlighter.write(self.dumper.visit(b))
             self.highlighter.normal()
 
     escape = "\33["
 
-    def delete(self, alo, ahi):
-        self.dump(self.delete_prefix, self.a, alo, ahi, self.normal_suffix)
-
-    def insert(self, blo, bhi):
-        self.dump(self.insert_prefix, self.b, blo, bhi, self.normal_suffix)
-
-    def equal(self, alo, ahi):
-        self.dump(self.equal_prefix, self.a, alo, ahi, self.normal_suffix)
-
-    def dump(self, prefix, x, lo, hi, suffix):
-        for i in xrange(lo, hi):
-            prefix()
-            self.highlighter.write(str(x[i]))
-            suffix()
-            self.highlighter.write('\n')
+    def delete(self, alo, ahi, blo, bhi):
+        assert alo < ahi
+        assert blo == bhi
+        for i in xrange(alo, ahi):
+            call = self.a[i]
+            self.highlighter.write('- ')
+            self.dumpCallNos(call.no, None)
+            self.highlighter.strike()
+            self.highlighter.color(self.delete_color)
+            self.dumpCall(call)
+
+    def insert(self, alo, ahi, blo, bhi):
+        assert alo == ahi
+        assert blo < bhi
+        for i in xrange(blo, bhi):
+            call = self.b[i]
+            self.highlighter.write('+ ')
+            self.dumpCallNos(None, call.no)
+            self.highlighter.color(self.insert_color)
+            self.dumpCall(call)
 
-    def delete_prefix(self):
-        self.highlighter.write('- ')
-        self.highlighter.color(self.delete_color)
-    
-    def insert_prefix(self):
-        self.highlighter.write('+ ')
-        self.highlighter.color(self.insert_color)
+    def equal(self, alo, ahi, blo, bhi):
+        assert alo < ahi and blo < bhi
+        assert ahi - alo == bhi - blo
+        for i in xrange(0, bhi - blo):
+            self.highlighter.write('  ')
+            a_call = self.a[alo + i]
+            b_call = self.b[blo + i]
+            assert a_call.functionName == b_call.functionName
+            assert len(a_call.args) == len(b_call.args)
+            self.dumpCallNos(a_call.no, b_call.no)
+            self.dumpCall(b_call)
 
-    def equal_prefix(self):
-        self.highlighter.write('  ')
+    def dumpCallNos(self, aNo, bNo):
+        if not self.callNos:
+            return
 
-    def normal_suffix(self):
+        if aNo is None:
+            self.highlighter.write(' '*self.aSpace)
+        else:
+            aNoStr = str(aNo)
+            self.highlighter.strike()
+            self.highlighter.color(self.delete_color)
+            self.highlighter.write(aNoStr)
+            self.highlighter.normal()
+            self.aSpace = len(aNoStr)
+        self.highlighter.write(' ')
+        if bNo is None:
+            self.highlighter.write(' '*self.bSpace)
+        else:
+            bNoStr = str(bNo)
+            self.highlighter.color(self.insert_color)
+            self.highlighter.write(bNoStr)
+            self.highlighter.normal()
+            self.bSpace = len(bNoStr)
+        self.highlighter.write(' ')
+
+    def dumpCall(self, call):
+        self.highlighter.bold(True)
+        self.highlighter.write(call.functionName)
+        self.highlighter.bold(False)
+        self.highlighter.write('(' + ', '.join(itertools.imap(self.dumper.visit, call.args)) + ')')
+        if call.ret is not None:
+            self.highlighter.write(' = ' + self.dumper.visit(call.ret))
         self.highlighter.normal()
+        self.highlighter.write('\n')
 
 
 def main():
@@ -213,31 +297,44 @@ def main():
         help='apitrace command [default: %default]')
     optparser.add_option(
         '-c', '--calls', metavar='CALLSET',
-        type="string", dest="calls", default='1-10000',
+        type="string", dest="calls", default='*',
         help="calls to compare [default: %default]")
-
+    optparser.add_option(
+        '--ref-calls', metavar='CALLSET',
+        type="string", dest="ref_calls", default=None,
+        help="calls to compare from reference trace")
+    optparser.add_option(
+        '--src-calls', metavar='CALLSET',
+        type="string", dest="src_calls", default=None,
+        help="calls to compare from source trace")
+    optparser.add_option(
+        '--call-nos',
+        action="store_true",
+        dest="call_nos", default=False,
+        help="dump call numbers")
     global options
     (options, args) = optparser.parse_args(sys.argv[1:])
     if len(args) != 2:
         optparser.error("incorrect number of arguments")
 
-    ref_calls = readtrace(args[0])
-    src_calls = readtrace(args[1])
+    if options.ref_calls is None:
+        options.ref_calls = options.calls
+    if options.src_calls is None:
+        options.src_calls = options.calls
+
+    ref_calls = readtrace(args[0], options.ref_calls)
+    src_calls = readtrace(args[1], options.src_calls)
 
     if sys.stdout.isatty():
-        less = subprocess.Popen(
-            args = ['less', '-FRXn'],
-            stdin = subprocess.PIPE
-        )
-        highlighter = Highlighter(less.stdin, True)
+        highlighter = LessHighlighter()
     else:
-        highlighter = Highlighter(sys.stdout)
-
-    differ = SDiffer(ref_calls, src_calls, highlighter)
-    differ.diff()
-    less.stdin.close()
+        highlighter = ColorHighlighter()
 
-    less.wait()
+    differ = SDiffer(ref_calls, src_calls, highlighter, options.call_nos)
+    try:
+        differ.diff()
+    except IOError:
+        pass
 
 
 if __name__ == '__main__':