]> git.cworth.org Git - apitrace/blob - scripts/tracediff2.py
Merge branch 'master' into d3dretrace
[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     calls = []
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             first  = self.insert(blo, bhi)
190             second = self.delete(alo, ahi)
191         else:
192             first  = self.delete(alo, ahi)
193             second = self.insert(blo, bhi)
194
195         for g in first, second:
196             for line in g:
197                 yield line
198
199     def replace_value(self, a, b):
200         if b == a:
201             self.highlighter.write(self.dumper.visit(b))
202         else:
203             self.highlighter.strike()
204             self.highlighter.color(self.delete_color)
205             self.highlighter.write(self.dumper.visit(a))
206             self.highlighter.normal()
207             self.highlighter.write(" ")
208             self.highlighter.color(self.insert_color)
209             self.highlighter.write(self.dumper.visit(b))
210             self.highlighter.normal()
211
212     escape = "\33["
213
214     def delete(self, alo, ahi, blo, bhi):
215         for i in xrange(alo, ahi):
216             call = self.a[i]
217             self.highlighter.write('- ')
218             self.dumpCallNos(call.no, None)
219             self.highlighter.strike()
220             self.highlighter.color(self.delete_color)
221             self.dumpCall(call)
222
223     def insert(self, alo, ahi, 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         for i in xrange(0, bhi - blo):
233             self.highlighter.write('  ')
234             a_call = self.a[alo + i]
235             b_call = self.b[blo + i]
236             assert a_call.functionName == b_call.functionName
237             assert len(a_call.args) == len(b_call.args)
238             self.dumpCallNos(a_call.no, b_call.no)
239             self.dumpCall(b_call)
240
241     def dumpCallNos(self, aNo, bNo):
242         if not self.callNos:
243             return
244
245         if aNo is None:
246             self.highlighter.write(' '*self.aSpace)
247         else:
248             aStr = str(aNo)
249             self.highlighter.strike()
250             self.highlighter.color(self.delete_color)
251             self.highlighter.write(str(aNo))
252             self.highlighter.normal()
253             self.aSpace = len(aStr)
254         self.highlighter.write(' ')
255         if bNo is None:
256             self.highlighter.write(' '*self.aSpace)
257         else:
258             bStr = str(bNo)
259             self.highlighter.color(self.insert_color)
260             self.highlighter.write(str(bNo))
261             self.highlighter.normal()
262             self.bSpace = len(bStr)
263         self.highlighter.write(' ')
264
265     def dumpCall(self, call):
266         self.highlighter.bold(True)
267         self.highlighter.write(call.functionName)
268         self.highlighter.bold(False)
269         self.highlighter.write('(' + ', '.join(itertools.imap(self.dumper.visit, call.args)) + ')')
270         if call.ret is not None:
271             self.highlighter.write(' = ' + self.dumper.visit(call.ret))
272         self.highlighter.normal()
273         self.highlighter.write('\n')
274
275
276 def main():
277     '''Main program.
278     '''
279
280     # Parse command line options
281     optparser = optparse.OptionParser(
282         usage='\n\t%prog <trace> <trace>',
283         version='%%prog')
284     optparser.add_option(
285         '-a', '--apitrace', metavar='PROGRAM',
286         type='string', dest='apitrace', default='apitrace',
287         help='apitrace command [default: %default]')
288     optparser.add_option(
289         '-c', '--calls', metavar='CALLSET',
290         type="string", dest="calls", default='*',
291         help="calls to compare [default: %default]")
292     optparser.add_option(
293         '--ref-calls', metavar='CALLSET',
294         type="string", dest="ref_calls", default=None,
295         help="calls to compare from reference trace")
296     optparser.add_option(
297         '--src-calls', metavar='CALLSET',
298         type="string", dest="src_calls", default=None,
299         help="calls to compare from source trace")
300     optparser.add_option(
301         '--call-nos',
302         action="store_true",
303         dest="call_nos", default=False,
304         help="dump call numbers")
305     global options
306     (options, args) = optparser.parse_args(sys.argv[1:])
307     if len(args) != 2:
308         optparser.error("incorrect number of arguments")
309
310     if options.ref_calls is None:
311         options.ref_calls = options.calls
312     if options.src_calls is None:
313         options.src_calls = options.calls
314
315     ref_calls = readtrace(args[0], options.ref_calls)
316     src_calls = readtrace(args[1], options.src_calls)
317
318     if sys.stdout.isatty():
319         highlighter = LessHighlighter()
320     else:
321         highlighter = ColorHighlighter()
322
323     differ = SDiffer(ref_calls, src_calls, highlighter, options.call_nos)
324     try:
325         differ.diff()
326     except IOError:
327         pass
328
329
330 if __name__ == '__main__':
331     main()