]> git.cworth.org Git - apitrace/blobdiff - scripts/tracediff.py
Merge branch 'd3dretrace'
[apitrace] / scripts / tracediff.py
index 2506eb752111faa793b127ddbd249f94bc730146..b3933a9d59ee1b11a5e6b0b9b81327345d9aa4ca 100755 (executable)
@@ -5,7 +5,7 @@
 # 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
+# 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, sublicense, and/or sell
 # copies of the Software, and to permit persons to whom the Software is
@@ -14,7 +14,7 @@
 # The above copyright notice and this permission notice 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
+# 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 NONINFRINGEMENT. IN NO EVENT SHALL THE
 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 ##########################################################################/
 
 
-import difflib
+import platform
 import optparse
 import os.path
+import shutil
+import subprocess
 import sys
-
-from trace import Parser
-
-try:
-    import debug
-except ImportError:
-    pass
-
-
-ignored_function_names = set([
-    'glGetString',
-    'glXGetClientString',
-    'glXGetCurrentDisplay',
-    'glXGetProcAddress',
-    'glXGetProcAddressARB',
-    'wglGetProcAddress',
-])
-
-
-def readtrace(trace):
-    calls = []
-    parser = Parser()
-    parser.open(trace)
-    call = parser.parse_call()
-    while call and len(calls) < 1000:
-        hash(call)
-        if call.sig.name not in ignored_function_names:
-            calls.append(call)
-        call = parser.parse_call()
-    return calls
-
-
-class SDiffer:
-
-    def __init__(self, a, b):
-        self.a = a
-        self.b = b
-
-    def diff(self):
-        matcher = difflib.SequenceMatcher(None, 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)
-            elif tag == 'insert':
-                self.insert(blo, bhi)
-            elif tag == 'equal':
-                self.equal(alo, ahi)
-            else:
-                raise ValueError, 'unknown tag %s' % (tag,)
-
-    def replace(self, alo, ahi, blo, bhi):
-        assert alo < ahi and blo < bhi
-        
-        a_names = [call.name for call in self.a[alo:ahi]]
-        b_names = [call.name for call in self.b[blo:bhi]]
-
-        matcher = difflib.SequenceMatcher(None, a_names, b_names)
-        for tag, _alo, _ahi, _blo, _bhi in matcher.get_opcodes():
-            _alo += alo
-            _ahi += alo
-            _blo += blo
-            _bhi += blo
-            if tag == 'replace':
-                self.replace_dissimilar(_alo, _ahi, _blo, _bhi)
-            elif tag == 'delete':
-                self.delete(_alo, _ahi)
-            elif tag == 'insert':
-                self.insert(_blo, _bhi)
-            elif tag == 'equal':
-                self.replace_similar(_alo, _ahi, _blo, _bhi)
-            else:
-                raise ValueError, 'unknown tag %s' % (tag,)
-
-    def replace_similar(self, alo, ahi, blo, bhi):
-        assert alo < ahi and blo < bhi
-        assert ahi - alo == bhi - blo
-        for i in xrange(0, bhi - blo):
-            a_call = self.a[alo + i]
-            b_call = self.b[blo + i]
-            assert a_call.name == b_call.name
-            assert len(a_call.args) == len(b_call.args)
-            sys.stdout.write(b_call.name + '(')
-            sep = ''
-            for j in xrange(len(b_call.args)):
-                sys.stdout.write(sep)
-                self.replace_value(a_call.args[j], b_call.args[j])
-                sep = ', '
-            sys.stdout.write(')')
-            if a_call.ret is not None or b_call.ret is not None:
-                sys.stdout.write(' = ')
-                self.replace_value(a_call.ret, b_call.ret)
-            sys.stdout.write('\n')
-
-    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)
-        else:
-            first  = self.delete(alo, ahi)
-            second = self.insert(blo, bhi)
-
-        for g in first, second:
-            for line in g:
-                yield line
-
-    def replace_value(self, a, b):
-        if b == a:
-            sys.stdout.write(str(b))
-        else:
-            sys.stdout.write('%s -> %s' % (a, b))
-
-    escape = "\33["
-
-    def delete(self, alo, ahi):
-        self.dump('- ' + self.escape + '9m', self.a, alo, ahi, self.escape + '0m')
-
-    def insert(self, blo, bhi):
-        self.dump('+ ', self.b, blo, bhi)
-
-    def equal(self, alo, ahi):
-        self.dump('  ' + self.escape + '2m', self.a, alo, ahi, self.escape + '0m')
-
-    def dump(self, prefix, x, lo, hi, suffix=""):
-        for i in xrange(lo, hi):
-            sys.stdout.write(prefix + str(x[i]) + suffix + '\n')
+import tempfile
+
+
+class Dumper:
+
+    def __init__(self, trace, calls):
+        self.output = tempfile.NamedTemporaryFile()
+
+        dump_args = [
+            options.apitrace,
+            'dump',
+            '--color=never',
+            '--call-nos=no',
+            '--arg-names=no',
+            '--calls=' + calls,
+            trace
+        ]
+
+        self.dump = subprocess.Popen(
+            args = dump_args,
+            stdout = self.output,
+            universal_newlines = True,
+        )
+
+
+if platform.system() == 'Windows':
+    start_delete = ''
+    end_delete   = ''
+    start_insert = ''
+    end_insert   = ''
+else:
+    start_delete = '\33[9m\33[31m'
+    end_delete   = '\33[0m'
+    start_insert = '\33[32m'
+    end_insert   = '\33[0m'
+
+
+def diff(ref_trace, src_trace):
+
+    ref_dumper = Dumper(ref_trace, options.ref_calls)
+    src_dumper = Dumper(src_trace, options.src_calls)
+
+    # TODO use difflib instead
+    if options.diff == 'diff':
+        diff_args = [
+                'diff',
+                '--speed-large-files',
+                '--old-line-format=' + start_delete + '%l' + end_delete + '\n',
+                '--new-line-format=' + start_insert + '%l' + end_insert + '\n',
+            ]
+    elif options.diff == 'sdiff':
+        diff_args = [
+                'sdiff',
+                '--width=%u' % options.width,
+                '--speed-large-files',
+            ]
+    elif options.diff == 'wdiff':
+        diff_args = [
+                'wdiff',
+                #'--terminal',
+                '--avoid-wraps',
+                '--start-delete=' + start_delete,
+                '--end-delete=' + end_delete,
+                '--start-insert=' + start_insert,
+                '--end-insert=' + end_insert,
+            ]
+    else:
+        assert False
+    diff_args += [ref_dumper.output.name, src_dumper.output.name]
+
+    ref_dumper.dump.wait()
+    src_dumper.dump.wait()
+
+    less = None
+    if sys.stdout.isatty():
+        less = subprocess.Popen(
+            args = ['less', '-FRXn'],
+            stdin = subprocess.PIPE
+        )
+
+        diff_stdout = less.stdin
+    else:
+        diff_stdout = None
+
+    diff = subprocess.Popen(
+        args = diff_args,
+        stdout = diff_stdout,
+        universal_newlines = True,
+    )
+
+    diff.wait()
+
+    if less is not None:
+        less.wait()
+
+
+def which(executable):
+    '''Search for the executable on the PATH.'''
+
+    if platform.system() == 'Windows':
+        exts = ['.exe']
+    else:
+        exts = ['']
+    dirs = os.environ['PATH'].split(os.path.pathsep)
+    for dir in dirs:
+        path = os.path.join(dir, executable)
+        for ext in exts:
+            if os.path.exists(path + ext):
+                return True
+    return False
+
+
+def columns():
+    import curses
+    curses.setupterm()
+    return curses.tigetnum('cols')
 
 
 def main():
-    global options
+    '''Main program.
+    '''
+
+    # Determine default options
+    default_width = columns()
 
+    # Parse command line options
     optparser = optparse.OptionParser(
-        usage='\n\t%prog <trace> <trace>',
+        usage='\n\t%prog [options] -- TRACE_FILE TRACE_FILE',
         version='%%prog')
     optparser.add_option(
-        '-d', '--tracedump', metavar='PROGRAM',
-        type='string', dest='tracedump', default='tracedump',
-        help='tracedump command [default: %default]')
+        '-a', '--apitrace', metavar='PROGRAM',
+        type='string', dest='apitrace', default='apitrace',
+        help='apitrace command [default: %default]')
+    optparser.add_option(
+        '-d', '--diff',
+        type="choice", choices=('diff', 'sdiff', 'wdiff'),
+        dest="diff", default=None,
+        help="diff program: wdiff, sdiff, or diff [default: auto]")
+    optparser.add_option(
+        '-c', '--calls', metavar='CALLSET',
+        type="string", dest="calls", default='1-10000',
+        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(
+        '-w', '--width', metavar='NUM',
+        type="string", dest="width", default=default_width,
+        help="columns [default: %default]")
 
+    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.diff is None:
+        if which('wdiff'):
+            options.diff = 'wdiff'
+        else:
+            sys.stderr.write('warning: wdiff not found\n')
+            if which('sdiff'):
+                options.diff = 'sdiff'
+            else:
+                sys.stderr.write('warning: sdiff not found\n')
+                options.diff = 'diff'
+
+    if options.ref_calls is None:
+        options.ref_calls = options.calls
+    if options.src_calls is None:
+        options.src_calls = options.calls
 
-    differ = SDiffer(ref_calls, src_calls)
-    differ.diff()
+    diff(*args)
 
 
 if __name__ == '__main__':