From baee57937dcb8acefd86f1b0edf3a6dfa96c047a Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jos=C3=A9=20Fonseca?= Date: Thu, 15 Mar 2012 00:09:25 +0000 Subject: [PATCH] Experimental pure-python trace diff. WIP. --- scripts/tracediff2.py | 205 ++++++++++++++++++++++++++++++++++++++++++ scripts/unpickle.py | 88 ++++++++++++++---- 2 files changed, 275 insertions(+), 18 deletions(-) create mode 100755 scripts/tracediff2.py diff --git a/scripts/tracediff2.py b/scripts/tracediff2.py new file mode 100755 index 0000000..c6a8682 --- /dev/null +++ b/scripts/tracediff2.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python +########################################################################## +# +# Copyright 2011 Jose Fonseca +# 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, sublicense, 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 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 NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS 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 difflib +import optparse +import os.path +import subprocess +import sys + +from unpickle import Unpickler + + +ignoredFunctionNames = set([ + 'glGetString', + 'glXGetClientString', + 'glXGetCurrentDisplay', + 'glXGetProcAddress', + 'glXGetProcAddressARB', + 'wglGetProcAddress', +]) + + +class Loader(Unpickler): + + def __init__(self, stream): + Unpickler.__init__(self, stream) + self.calls = [] + + def handleCall(self, call): + call.no = None + hash(call) + if call.functionName not in ignoredFunctionNames: + self.calls.append(call) + + +def readtrace(trace): + p = subprocess.Popen( + args = [ + options.apitrace, + 'pickle', + '--calls=' + options.calls, + trace + ], + stdout = subprocess.PIPE, + ) + + calls = [] + parser = Loader(p.stdout) + parser.parse() + return parser.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.functionName for call in self.a[alo:ahi]] + b_names = [call.functionName 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.functionName == b_call.functionName + assert len(a_call.args) == len(b_call.args) + sys.stdout.write(' ' + b_call.functionName + '(') + 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') + + +def main(): + '''Main program. + ''' + + # Parse command line options + optparser = optparse.OptionParser( + usage='\n\t%prog ', + version='%%prog') + optparser.add_option( + '-a', '--apitrace', metavar='PROGRAM', + type='string', dest='apitrace', default='apitrace', + help='apitrace command [default: %default]') + optparser.add_option( + '-c', '--calls', metavar='CALLSET', + type="string", dest="calls", default='1-10000', + help="calls to compare [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]) + + differ = SDiffer(ref_calls, src_calls) + differ.diff() + + +if __name__ == '__main__': + main() diff --git a/scripts/unpickle.py b/scripts/unpickle.py index a3c274c..fd7989b 100755 --- a/scripts/unpickle.py +++ b/scripts/unpickle.py @@ -39,6 +39,73 @@ import sys import time +class Call: + + def __init__(self, callTuple): + self.no, self.functionName, self.args, self.ret = callTuple + + def __str__(self): + s = self.functionName + if self.no is not None: + s = str(self.no) + ' ' + s + s += '(' + ', '.join(map(repr, self.args)) + ')' + if self.ret is not None: + s += ' = ' + s += repr(self.ret) + return s + + def __eq__(self, other): + return \ + self.functionName == other.functionName and \ + self.args == other.args and \ + self.ret == other.ret + + def __hash__(self): + # XXX: hack due to unhashable types + #return hash(self.functionName) ^ hash(tuple(self.args)) ^ hash(self.ret) + return hash(self.functionName) ^ hash(repr(self.args)) ^ hash(repr(self.ret)) + + + +class Unpickler: + + callFactory = Call + + def __init__(self, stream): + self.stream = stream + + def parse(self): + while self.parseCall(): + pass + + def parseCall(self): + try: + callTuple = pickle.load(self.stream) + except EOFError: + return False + else: + call = self.callFactory(callTuple) + self.handleCall(call) + return True + + def handleCall(self, call): + pass + + +class Counter(Unpickler): + + def __init__(self, stream, quiet): + Unpickler.__init__(self, stream) + self.quiet = quiet + self.calls = 0 + + def handleCall(self, call): + if not self.quiet: + sys.stdout.write(str(call)) + sys.stdout.write('\n') + self.calls += 1 + + def main(): optparser = optparse.OptionParser( usage="\n\tapitrace pickle trace. %prog [options]") @@ -61,27 +128,12 @@ def main(): import os msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) - calls = 0 startTime = time.time() - while True: - try: - call = pickle.load(sys.stdin) - except EOFError: - break - else: - callNo, functionName, args, ret = call - if not options.quiet: - sys.stdout.write('%u ' % callNo) - sys.stdout.write(functionName) - sys.stdout.write('(' + ', '.join(map(repr, args)) + ')') - if ret is not None: - sys.stdout.write(' = ') - sys.stdout.write(repr(ret)) - sys.stdout.write('\n') - calls += 1 + parser = Counter(sys.stdin, options.quiet) + parser.parse() stopTime = time.time() duration = stopTime - startTime - sys.stderr.write('%u calls, %.03f secs, %u calls/sec\n' % (calls, duration, calls/duration)) + sys.stderr.write('%u calls, %.03f secs, %u calls/sec\n' % (parser.calls, duration, parser.calls/duration)) if __name__ == '__main__': -- 2.43.0