2 ##########################################################################
4 # Copyright 2011-2012 Jose Fonseca
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:
14 # The above copyright notice and this permission notice shall be included in
15 # all copies or substantial portions of the Software.
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
25 ##########################################################################/
35 from unpickle import Unpickler, Dumper, Rebuilder
36 from highlight import ColorHighlighter, LessHighlighter
39 ignoredFunctionNames = set([
42 'glXGetCurrentDisplay',
43 'glXGetCurrentContext',
45 'glXGetProcAddressARB',
51 '''Data-less proxy for bytearrays, to save memory.'''
53 def __init__(self, size, hash):
58 return 'blob(%u)' % self.size
60 def __eq__(self, other):
61 return isinstance(other, Blob) and self.size == other.size and self.hash == other.hash
67 class BlobReplacer(Rebuilder):
68 '''Replace blobs with proxys.'''
70 def visitByteArray(self, obj):
71 return Blob(len(obj), hash(str(obj)))
73 def visitCall(self, call):
74 call.args = map(self.visit, call.args)
75 call.ret = self.visit(call.ret)
78 class Loader(Unpickler):
80 def __init__(self, stream):
81 Unpickler.__init__(self, stream)
83 self.rebuilder = BlobReplacer()
85 def handleCall(self, call):
86 if call.functionName not in ignoredFunctionNames:
87 self.rebuilder.visitCall(call)
88 self.calls.append(call)
91 def readtrace(trace, calls):
100 stdout = subprocess.PIPE,
103 parser = Loader(p.stdout)
110 def __init__(self, a, b, highlighter, callNos = False):
113 self.highlighter = highlighter
114 self.delete_color = highlighter.red
115 self.insert_color = highlighter.green
116 self.callNos = callNos
119 self.dumper = Dumper()
122 matcher = difflib.SequenceMatcher(self.isjunk, self.a, self.b)
123 for tag, alo, ahi, blo, bhi in matcher.get_opcodes():
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)
131 self.equal(alo, ahi, blo, bhi)
133 raise ValueError, 'unknown tag %s' % (tag,)
135 def isjunk(self, call):
136 return call.functionName == 'glGetError' and call.ret in ('GL_NO_ERROR', 0)
138 def replace(self, alo, ahi, blo, bhi):
139 assert alo < ahi and blo < bhi
141 a_names = [call.functionName for call in self.a[alo:ahi]]
142 b_names = [call.functionName for call in self.b[blo:bhi]]
144 matcher = difflib.SequenceMatcher(None, a_names, b_names)
145 for tag, _alo, _ahi, _blo, _bhi in matcher.get_opcodes():
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)
157 self.replace_similar(_alo, _ahi, _blo, _bhi)
159 raise ValueError, 'unknown tag %s' % (tag,)
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 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('(')
175 numArgs = max(len(a_call.args), len(b_call.args))
176 for j in xrange(numArgs):
177 self.highlighter.write(sep)
179 a_arg = a_call.args[j]
183 b_arg = b_call.args[j]
186 self.replace_value(a_arg, b_arg)
188 self.highlighter.write(')')
189 if a_call.ret is not None or b_call.ret is not None:
190 self.highlighter.write(' = ')
191 self.replace_value(a_call.ret, b_call.ret)
192 self.highlighter.write('\n')
194 def replace_dissimilar(self, alo, ahi, blo, bhi):
195 assert alo < ahi and blo < bhi
196 if bhi - blo < ahi - alo:
197 self.insert(alo, alo, blo, bhi)
198 self.delete(alo, ahi, bhi, bhi)
200 self.delete(alo, ahi, blo, blo)
201 self.insert(ahi, ahi, blo, bhi)
203 def replace_value(self, a, b):
205 self.highlighter.write(self.dumper.visit(b))
207 self.highlighter.strike()
208 self.highlighter.color(self.delete_color)
209 self.highlighter.write(self.dumper.visit(a))
210 self.highlighter.normal()
211 self.highlighter.write(" ")
212 self.highlighter.color(self.insert_color)
213 self.highlighter.write(self.dumper.visit(b))
214 self.highlighter.normal()
218 def delete(self, alo, ahi, blo, bhi):
221 for i in xrange(alo, ahi):
223 self.highlighter.write('- ')
224 self.dumpCallNos(call.no, None)
225 self.highlighter.strike()
226 self.highlighter.color(self.delete_color)
229 def insert(self, alo, ahi, blo, bhi):
232 for i in xrange(blo, bhi):
234 self.highlighter.write('+ ')
235 self.dumpCallNos(None, call.no)
236 self.highlighter.color(self.insert_color)
239 def equal(self, alo, ahi, blo, bhi):
240 assert alo < ahi and blo < bhi
241 assert ahi - alo == bhi - blo
242 for i in xrange(0, bhi - blo):
243 self.highlighter.write(' ')
244 a_call = self.a[alo + i]
245 b_call = self.b[blo + i]
246 assert a_call.functionName == b_call.functionName
247 assert len(a_call.args) == len(b_call.args)
248 self.dumpCallNos(a_call.no, b_call.no)
249 self.dumpCall(b_call)
251 def dumpCallNos(self, aNo, bNo):
256 self.highlighter.write(' '*self.aSpace)
259 self.highlighter.strike()
260 self.highlighter.color(self.delete_color)
261 self.highlighter.write(aNoStr)
262 self.highlighter.normal()
263 self.aSpace = len(aNoStr)
264 self.highlighter.write(' ')
266 self.highlighter.write(' '*self.bSpace)
269 self.highlighter.color(self.insert_color)
270 self.highlighter.write(bNoStr)
271 self.highlighter.normal()
272 self.bSpace = len(bNoStr)
273 self.highlighter.write(' ')
275 def dumpCall(self, call):
276 self.highlighter.bold(True)
277 self.highlighter.write(call.functionName)
278 self.highlighter.bold(False)
279 self.highlighter.write('(' + ', '.join(itertools.imap(self.dumper.visit, call.args)) + ')')
280 if call.ret is not None:
281 self.highlighter.write(' = ' + self.dumper.visit(call.ret))
282 self.highlighter.normal()
283 self.highlighter.write('\n')
290 # Parse command line options
291 optparser = optparse.OptionParser(
292 usage='\n\t%prog <trace> <trace>',
294 optparser.add_option(
295 '-a', '--apitrace', metavar='PROGRAM',
296 type='string', dest='apitrace', default='apitrace',
297 help='apitrace command [default: %default]')
298 optparser.add_option(
299 '-c', '--calls', metavar='CALLSET',
300 type="string", dest="calls", default='*',
301 help="calls to compare [default: %default]")
302 optparser.add_option(
303 '--ref-calls', metavar='CALLSET',
304 type="string", dest="ref_calls", default=None,
305 help="calls to compare from reference trace")
306 optparser.add_option(
307 '--src-calls', metavar='CALLSET',
308 type="string", dest="src_calls", default=None,
309 help="calls to compare from source trace")
310 optparser.add_option(
313 dest="call_nos", default=False,
314 help="dump call numbers")
316 (options, args) = optparser.parse_args(sys.argv[1:])
318 optparser.error("incorrect number of arguments")
320 if options.ref_calls is None:
321 options.ref_calls = options.calls
322 if options.src_calls is None:
323 options.src_calls = options.calls
325 ref_calls = readtrace(args[0], options.ref_calls)
326 src_calls = readtrace(args[1], options.src_calls)
328 if sys.stdout.isatty():
329 highlighter = LessHighlighter()
331 highlighter = ColorHighlighter()
333 differ = SDiffer(ref_calls, src_calls, highlighter, options.call_nos)
340 if __name__ == '__main__':