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