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