]> git.cworth.org Git - apitrace/blob - scripts/tracediff2.py
Experimental pure-python trace diff.
[apitrace] / scripts / tracediff2.py
1 #!/usr/bin/env python
2 ##########################################################################
3 #
4 # Copyright 2011 Jose Fonseca
5 # All Rights Reserved.
6 #
7 # Permission is hereby granted, free of charge, to any person obtaining a copy
8 # of this software and associated documentation files (the "Software"), to deal
9 # in the Software without restriction, including without limitation the rights
10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 # copies of the Software, and to permit persons to whom the Software is
12 # furnished to do so, subject to the following conditions:
13 #
14 # The above copyright notice and this permission notice shall be included in
15 # all copies or substantial portions of the Software.
16 #
17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 # THE SOFTWARE.
24 #
25 ##########################################################################/
26
27
28 import difflib
29 import optparse
30 import os.path
31 import subprocess
32 import sys
33
34 from unpickle import Unpickler
35
36
37 ignoredFunctionNames = set([
38     'glGetString',
39     'glXGetClientString',
40     'glXGetCurrentDisplay',
41     'glXGetProcAddress',
42     'glXGetProcAddressARB',
43     'wglGetProcAddress',
44 ])
45
46
47 class Loader(Unpickler):
48
49     def __init__(self, stream):
50         Unpickler.__init__(self, stream)
51         self.calls = []
52
53     def handleCall(self, call):
54         call.no = None
55         hash(call)
56         if call.functionName not in ignoredFunctionNames:
57             self.calls.append(call)
58
59
60 def readtrace(trace):
61     p = subprocess.Popen(
62         args = [
63             options.apitrace,
64             'pickle',
65             '--calls=' + options.calls,
66             trace
67         ],
68         stdout = subprocess.PIPE,
69     )
70
71     calls = []
72     parser = Loader(p.stdout)
73     parser.parse()
74     return parser.calls
75
76
77 class SDiffer:
78
79     def __init__(self, a, b):
80         self.a = a
81         self.b = b
82
83     def diff(self):
84         matcher = difflib.SequenceMatcher(None, self.a, self.b)
85         for tag, alo, ahi, blo, bhi in matcher.get_opcodes():
86             if tag == 'replace':
87                 self.replace(alo, ahi, blo, bhi)
88             elif tag == 'delete':
89                 self.delete(alo, ahi)
90             elif tag == 'insert':
91                 self.insert(blo, bhi)
92             elif tag == 'equal':
93                 self.equal(alo, ahi)
94             else:
95                 raise ValueError, 'unknown tag %s' % (tag,)
96
97     def replace(self, alo, ahi, blo, bhi):
98         assert alo < ahi and blo < bhi
99         
100         a_names = [call.functionName for call in self.a[alo:ahi]]
101         b_names = [call.functionName for call in self.b[blo:bhi]]
102
103         matcher = difflib.SequenceMatcher(None, a_names, b_names)
104         for tag, _alo, _ahi, _blo, _bhi in matcher.get_opcodes():
105             _alo += alo
106             _ahi += alo
107             _blo += blo
108             _bhi += blo
109             if tag == 'replace':
110                 self.replace_dissimilar(_alo, _ahi, _blo, _bhi)
111             elif tag == 'delete':
112                 self.delete(_alo, _ahi)
113             elif tag == 'insert':
114                 self.insert(_blo, _bhi)
115             elif tag == 'equal':
116                 self.replace_similar(_alo, _ahi, _blo, _bhi)
117             else:
118                 raise ValueError, 'unknown tag %s' % (tag,)
119
120     def replace_similar(self, alo, ahi, blo, bhi):
121         assert alo < ahi and blo < bhi
122         assert ahi - alo == bhi - blo
123         for i in xrange(0, bhi - blo):
124             a_call = self.a[alo + i]
125             b_call = self.b[blo + i]
126             assert a_call.functionName == b_call.functionName
127             assert len(a_call.args) == len(b_call.args)
128             sys.stdout.write('  ' + b_call.functionName + '(')
129             sep = ''
130             for j in xrange(len(b_call.args)):
131                 sys.stdout.write(sep)
132                 self.replace_value(a_call.args[j], b_call.args[j])
133                 sep = ', '
134             sys.stdout.write(')')
135             if a_call.ret is not None or b_call.ret is not None:
136                 sys.stdout.write(' = ')
137                 self.replace_value(a_call.ret, b_call.ret)
138             sys.stdout.write('\n')
139
140     def replace_dissimilar(self, alo, ahi, blo, bhi):
141         assert alo < ahi and blo < bhi
142         if bhi - blo < ahi - alo:
143             first  = self.insert(blo, bhi)
144             second = self.delete(alo, ahi)
145         else:
146             first  = self.delete(alo, ahi)
147             second = self.insert(blo, bhi)
148
149         for g in first, second:
150             for line in g:
151                 yield line
152
153     def replace_value(self, a, b):
154         if b == a:
155             sys.stdout.write(str(b))
156         else:
157             sys.stdout.write('%s -> %s' % (a, b))
158
159     escape = "\33["
160
161     def delete(self, alo, ahi):
162         self.dump('- ' + self.escape + '9m', self.a, alo, ahi, self.escape + '0m')
163
164     def insert(self, blo, bhi):
165         self.dump('+ ', self.b, blo, bhi)
166
167     def equal(self, alo, ahi):
168         self.dump('  ' + self.escape + '2m', self.a, alo, ahi, self.escape + '0m')
169
170     def dump(self, prefix, x, lo, hi, suffix=""):
171         for i in xrange(lo, hi):
172             sys.stdout.write(prefix + str(x[i]) + suffix + '\n')
173
174
175 def main():
176     '''Main program.
177     '''
178
179     # Parse command line options
180     optparser = optparse.OptionParser(
181         usage='\n\t%prog <trace> <trace>',
182         version='%%prog')
183     optparser.add_option(
184         '-a', '--apitrace', metavar='PROGRAM',
185         type='string', dest='apitrace', default='apitrace',
186         help='apitrace command [default: %default]')
187     optparser.add_option(
188         '-c', '--calls', metavar='CALLSET',
189         type="string", dest="calls", default='1-10000',
190         help="calls to compare [default: %default]")
191
192     global options
193     (options, args) = optparser.parse_args(sys.argv[1:])
194     if len(args) != 2:
195         optparser.error("incorrect number of arguments")
196
197     ref_calls = readtrace(args[0])
198     src_calls = readtrace(args[1])
199
200     differ = SDiffer(ref_calls, src_calls)
201     differ.diff()
202
203
204 if __name__ == '__main__':
205     main()