]> git.cworth.org Git - apitrace/blob - scripts/tracediff2.py
apitrace diff-images: Print one line for each file being compared.
[apitrace] / scripts / tracediff2.py
1 #!/usr/bin/env python
2 ##########################################################################
3 #
4 # Copyright 2011-2012 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 itertools
30 import optparse
31 import os.path
32 import subprocess
33 import sys
34
35 from unpickle import Unpickler, Dumper, Rebuilder
36 from highlight import ColorHighlighter, LessHighlighter
37
38
39 ignoredFunctionNames = set([
40     'glGetString',
41     'glXGetClientString',
42     'glXGetCurrentDisplay',
43     'glXGetCurrentContext',
44     'glXGetProcAddress',
45     'glXGetProcAddressARB',
46     'wglGetProcAddress',
47 ])
48
49
50 class Blob:
51     '''Data-less proxy for bytearrays, to save memory.'''
52
53     def __init__(self, size, hash):
54         self.size = size
55         self.hash = hash
56
57     def __repr__(self):
58         return 'blob(%u)' % self.size
59
60     def __eq__(self, other):
61         return self.size == other.size and self.hash == other.hash
62
63     def __hash__(self):
64         return self.hash
65
66
67 class BlobReplacer(Rebuilder):
68     '''Replace blobs with proxys.'''
69
70     def visitByteArray(self, obj):
71         return Blob(len(obj), hash(str(obj)))
72
73     def visitCall(self, call):
74         call.args = map(self.visit, call.args)
75         call.ret = self.visit(call.ret)
76
77
78 class Loader(Unpickler):
79
80     def __init__(self, stream):
81         Unpickler.__init__(self, stream)
82         self.calls = []
83         self.rebuilder = BlobReplacer()
84
85     def handleCall(self, call):
86         if call.functionName not in ignoredFunctionNames:
87             self.rebuilder.visitCall(call)
88             self.calls.append(call)
89
90
91 def readtrace(trace, calls):
92     p = subprocess.Popen(
93         args = [
94             options.apitrace,
95             'pickle',
96             '--symbolic',
97             '--calls=' + calls,
98             trace
99         ],
100         stdout = subprocess.PIPE,
101     )
102
103     parser = Loader(p.stdout)
104     parser.parse()
105     return parser.calls
106
107
108 class SDiffer:
109
110     def __init__(self, a, b, highlighter, callNos = False):
111         self.a = a
112         self.b = b
113         self.highlighter = highlighter
114         self.delete_color = highlighter.red
115         self.insert_color = highlighter.green
116         self.callNos = callNos
117         self.aSpace = 0
118         self.bSpace = 0
119         self.dumper = Dumper()
120
121     def diff(self):
122         matcher = difflib.SequenceMatcher(self.isjunk, self.a, self.b)
123         for tag, alo, ahi, blo, bhi in matcher.get_opcodes():
124             if tag == 'replace':
125                 self.replace(alo, ahi, blo, bhi)
126             elif tag == 'delete':
127                 self.delete(alo, ahi, blo, bhi)
128             elif tag == 'insert':
129                 self.insert(alo, ahi, blo, bhi)
130             elif tag == 'equal':
131                 self.equal(alo, ahi, blo, bhi)
132             else:
133                 raise ValueError, 'unknown tag %s' % (tag,)
134
135     def isjunk(self, call):
136         return call.functionName == 'glGetError' and call.ret in ('GL_NO_ERROR', 0)
137
138     def replace(self, alo, ahi, blo, bhi):
139         assert alo < ahi and blo < bhi
140         
141         a_names = [call.functionName for call in self.a[alo:ahi]]
142         b_names = [call.functionName for call in self.b[blo:bhi]]
143
144         matcher = difflib.SequenceMatcher(None, a_names, b_names)
145         for tag, _alo, _ahi, _blo, _bhi in matcher.get_opcodes():
146             _alo += alo
147             _ahi += alo
148             _blo += blo
149             _bhi += blo
150             if tag == 'replace':
151                 self.replace_dissimilar(_alo, _ahi, _blo, _bhi)
152             elif tag == 'delete':
153                 self.delete(_alo, _ahi, _blo, _bhi)
154             elif tag == 'insert':
155                 self.insert(_alo, _ahi, _blo, _bhi)
156             elif tag == 'equal':
157                 self.replace_similar(_alo, _ahi, _blo, _bhi)
158             else:
159                 raise ValueError, 'unknown tag %s' % (tag,)
160
161     def replace_similar(self, alo, ahi, blo, bhi):
162         assert alo < ahi and blo < bhi
163         assert ahi - alo == bhi - blo
164         for i in xrange(0, bhi - blo):
165             self.highlighter.write('| ')
166             a_call = self.a[alo + i]
167             b_call = self.b[blo + i]
168             assert a_call.functionName == b_call.functionName
169             assert len(a_call.args) == len(b_call.args)
170             self.dumpCallNos(a_call.no, b_call.no)
171             self.highlighter.bold(True)
172             self.highlighter.write(b_call.functionName)
173             self.highlighter.bold(False)
174             self.highlighter.write('(')
175             sep = ''
176             for j in xrange(len(b_call.args)):
177                 self.highlighter.write(sep)
178                 self.replace_value(a_call.args[j], b_call.args[j])
179                 sep = ', '
180             self.highlighter.write(')')
181             if a_call.ret is not None or b_call.ret is not None:
182                 self.highlighter.write(' = ')
183                 self.replace_value(a_call.ret, b_call.ret)
184             self.highlighter.write('\n')
185
186     def replace_dissimilar(self, alo, ahi, blo, bhi):
187         assert alo < ahi and blo < bhi
188         if bhi - blo < ahi - alo:
189             self.insert(alo, alo, blo, bhi)
190             self.delete(alo, ahi, bhi, bhi)
191         else:
192             self.delete(alo, ahi, blo, blo)
193             self.insert(ahi, ahi, blo, bhi)
194
195     def replace_value(self, a, b):
196         if b == a:
197             self.highlighter.write(self.dumper.visit(b))
198         else:
199             self.highlighter.strike()
200             self.highlighter.color(self.delete_color)
201             self.highlighter.write(self.dumper.visit(a))
202             self.highlighter.normal()
203             self.highlighter.write(" ")
204             self.highlighter.color(self.insert_color)
205             self.highlighter.write(self.dumper.visit(b))
206             self.highlighter.normal()
207
208     escape = "\33["
209
210     def delete(self, alo, ahi, blo, bhi):
211         assert alo < ahi
212         assert blo == bhi
213         for i in xrange(alo, ahi):
214             call = self.a[i]
215             self.highlighter.write('- ')
216             self.dumpCallNos(call.no, None)
217             self.highlighter.strike()
218             self.highlighter.color(self.delete_color)
219             self.dumpCall(call)
220
221     def insert(self, alo, ahi, blo, bhi):
222         assert alo == ahi
223         assert blo < bhi
224         for i in xrange(blo, bhi):
225             call = self.b[i]
226             self.highlighter.write('+ ')
227             self.dumpCallNos(None, call.no)
228             self.highlighter.color(self.insert_color)
229             self.dumpCall(call)
230
231     def equal(self, alo, ahi, blo, bhi):
232         assert alo < ahi and blo < bhi
233         assert ahi - alo == bhi - blo
234         for i in xrange(0, bhi - blo):
235             self.highlighter.write('  ')
236             a_call = self.a[alo + i]
237             b_call = self.b[blo + i]
238             assert a_call.functionName == b_call.functionName
239             assert len(a_call.args) == len(b_call.args)
240             self.dumpCallNos(a_call.no, b_call.no)
241             self.dumpCall(b_call)
242
243     def dumpCallNos(self, aNo, bNo):
244         if not self.callNos:
245             return
246
247         if aNo is None:
248             self.highlighter.write(' '*self.aSpace)
249         else:
250             aNoStr = str(aNo)
251             self.highlighter.strike()
252             self.highlighter.color(self.delete_color)
253             self.highlighter.write(aNoStr)
254             self.highlighter.normal()
255             self.aSpace = len(aNoStr)
256         self.highlighter.write(' ')
257         if bNo is None:
258             self.highlighter.write(' '*self.bSpace)
259         else:
260             bNoStr = str(bNo)
261             self.highlighter.color(self.insert_color)
262             self.highlighter.write(bNoStr)
263             self.highlighter.normal()
264             self.bSpace = len(bNoStr)
265         self.highlighter.write(' ')
266
267     def dumpCall(self, call):
268         self.highlighter.bold(True)
269         self.highlighter.write(call.functionName)
270         self.highlighter.bold(False)
271         self.highlighter.write('(' + ', '.join(itertools.imap(self.dumper.visit, call.args)) + ')')
272         if call.ret is not None:
273             self.highlighter.write(' = ' + self.dumper.visit(call.ret))
274         self.highlighter.normal()
275         self.highlighter.write('\n')
276
277
278 def main():
279     '''Main program.
280     '''
281
282     # Parse command line options
283     optparser = optparse.OptionParser(
284         usage='\n\t%prog <trace> <trace>',
285         version='%%prog')
286     optparser.add_option(
287         '-a', '--apitrace', metavar='PROGRAM',
288         type='string', dest='apitrace', default='apitrace',
289         help='apitrace command [default: %default]')
290     optparser.add_option(
291         '-c', '--calls', metavar='CALLSET',
292         type="string", dest="calls", default='*',
293         help="calls to compare [default: %default]")
294     optparser.add_option(
295         '--ref-calls', metavar='CALLSET',
296         type="string", dest="ref_calls", default=None,
297         help="calls to compare from reference trace")
298     optparser.add_option(
299         '--src-calls', metavar='CALLSET',
300         type="string", dest="src_calls", default=None,
301         help="calls to compare from source trace")
302     optparser.add_option(
303         '--call-nos',
304         action="store_true",
305         dest="call_nos", default=False,
306         help="dump call numbers")
307     global options
308     (options, args) = optparser.parse_args(sys.argv[1:])
309     if len(args) != 2:
310         optparser.error("incorrect number of arguments")
311
312     if options.ref_calls is None:
313         options.ref_calls = options.calls
314     if options.src_calls is None:
315         options.src_calls = options.calls
316
317     ref_calls = readtrace(args[0], options.ref_calls)
318     src_calls = readtrace(args[1], options.src_calls)
319
320     if sys.stdout.isatty():
321         highlighter = LessHighlighter()
322     else:
323         highlighter = ColorHighlighter()
324
325     differ = SDiffer(ref_calls, src_calls, highlighter, options.call_nos)
326     try:
327         differ.diff()
328     except IOError:
329         pass
330
331
332 if __name__ == '__main__':
333     main()