]> git.cworth.org Git - apitrace/blob - scripts/tracediff2.py
001f3226aaed5f117ad400e8a2f90cc2e8cc2ffb
[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 Highlighter
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         call.no = None
56         hash(call)
57         if call.functionName not in ignoredFunctionNames:
58             self.calls.append(call)
59
60
61 def readtrace(trace):
62     p = subprocess.Popen(
63         args = [
64             options.apitrace,
65             'pickle',
66             '--calls=' + options.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):
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
87     def diff(self):
88         matcher = difflib.SequenceMatcher(None, self.a, self.b)
89         for tag, alo, ahi, blo, bhi in matcher.get_opcodes():
90             if tag == 'replace':
91                 self.replace(alo, ahi, blo, bhi)
92             elif tag == 'delete':
93                 self.delete(alo, ahi)
94             elif tag == 'insert':
95                 self.insert(blo, bhi)
96             elif tag == 'equal':
97                 self.equal(alo, ahi)
98             else:
99                 raise ValueError, 'unknown tag %s' % (tag,)
100
101     def replace(self, alo, ahi, blo, bhi):
102         assert alo < ahi and blo < bhi
103         
104         a_names = [call.functionName for call in self.a[alo:ahi]]
105         b_names = [call.functionName for call in self.b[blo:bhi]]
106
107         matcher = difflib.SequenceMatcher(None, a_names, b_names)
108         for tag, _alo, _ahi, _blo, _bhi in matcher.get_opcodes():
109             _alo += alo
110             _ahi += alo
111             _blo += blo
112             _bhi += blo
113             if tag == 'replace':
114                 self.replace_dissimilar(_alo, _ahi, _blo, _bhi)
115             elif tag == 'delete':
116                 self.delete(_alo, _ahi)
117             elif tag == 'insert':
118                 self.insert(_blo, _bhi)
119             elif tag == 'equal':
120                 self.replace_similar(_alo, _ahi, _blo, _bhi)
121             else:
122                 raise ValueError, 'unknown tag %s' % (tag,)
123
124     def replace_similar(self, alo, ahi, blo, bhi):
125         assert alo < ahi and blo < bhi
126         assert ahi - alo == bhi - blo
127         for i in xrange(0, bhi - blo):
128             a_call = self.a[alo + i]
129             b_call = self.b[blo + i]
130             assert a_call.functionName == b_call.functionName
131             assert len(a_call.args) == len(b_call.args)
132             self.equal_prefix()
133             self.highlighter.bold(True)
134             self.highlighter.write(b_call.functionName)
135             self.highlighter.bold(False)
136             sep = ''
137             for j in xrange(len(b_call.args)):
138                 self.highlighter.write(sep)
139                 self.replace_value(a_call.args[j], b_call.args[j])
140                 sep = ', '
141             self.highlighter.write(')')
142             if a_call.ret is not None or b_call.ret is not None:
143                 self.highlighter.write(' = ')
144                 self.replace_value(a_call.ret, b_call.ret)
145             self.highlighter.write('\n')
146
147     def replace_dissimilar(self, alo, ahi, blo, bhi):
148         assert alo < ahi and blo < bhi
149         if bhi - blo < ahi - alo:
150             first  = self.insert(blo, bhi)
151             second = self.delete(alo, ahi)
152         else:
153             first  = self.delete(alo, ahi)
154             second = self.insert(blo, bhi)
155
156         for g in first, second:
157             for line in g:
158                 yield line
159
160     def replace_value(self, a, b):
161         if b == a:
162             self.highlighter.write(str(b))
163         else:
164             self.highlighter.color(self.delete_color)
165             self.highlighter.write(str(b))
166             self.highlighter.normal()
167             self.highlighter.write(" -> ")
168             self.highlighter.color(self.insert_color)
169             self.highlighter.write(str(b))
170             self.highlighter.normal()
171
172     escape = "\33["
173
174     def delete(self, alo, ahi):
175         self.dump(self.delete_prefix, self.a, alo, ahi, self.normal_suffix)
176
177     def insert(self, blo, bhi):
178         self.dump(self.insert_prefix, self.b, blo, bhi, self.normal_suffix)
179
180     def equal(self, alo, ahi):
181         self.dump(self.equal_prefix, self.a, alo, ahi, self.normal_suffix)
182
183     def dump(self, prefix, x, lo, hi, suffix):
184         for i in xrange(lo, hi):
185             call = x[i]
186             prefix()
187             self.highlighter.bold(True)
188             self.highlighter.write(call.functionName)
189             self.highlighter.bold(False)
190             self.highlighter.write('(' + ', '.join(map(repr, call.args)) + ')')
191             if call.ret is not None:
192                 self.highlighter.write(' = ' + repr(call.ret))
193             suffix()
194             self.highlighter.write('\n')
195
196     def delete_prefix(self):
197         self.highlighter.write('- ')
198         self.highlighter.color(self.delete_color)
199     
200     def insert_prefix(self):
201         self.highlighter.write('+ ')
202         self.highlighter.color(self.insert_color)
203
204     def equal_prefix(self):
205         self.highlighter.write('  ')
206
207     def normal_suffix(self):
208         self.highlighter.normal()
209
210
211 def main():
212     '''Main program.
213     '''
214
215     # Parse command line options
216     optparser = optparse.OptionParser(
217         usage='\n\t%prog <trace> <trace>',
218         version='%%prog')
219     optparser.add_option(
220         '-a', '--apitrace', metavar='PROGRAM',
221         type='string', dest='apitrace', default='apitrace',
222         help='apitrace command [default: %default]')
223     optparser.add_option(
224         '-c', '--calls', metavar='CALLSET',
225         type="string", dest="calls", default='1-10000',
226         help="calls to compare [default: %default]")
227
228     global options
229     (options, args) = optparser.parse_args(sys.argv[1:])
230     if len(args) != 2:
231         optparser.error("incorrect number of arguments")
232
233     ref_calls = readtrace(args[0])
234     src_calls = readtrace(args[1])
235
236     if sys.stdout.isatty():
237         less = subprocess.Popen(
238             args = ['less', '-FRXn'],
239             stdin = subprocess.PIPE
240         )
241         highlighter = Highlighter(less.stdin, True)
242     else:
243         highlighter = Highlighter(sys.stdout)
244
245     differ = SDiffer(ref_calls, src_calls, highlighter)
246     differ.diff()
247     less.stdin.close()
248
249     less.wait()
250
251
252 if __name__ == '__main__':
253     main()