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