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
36 from highlight import ColorHighlighter, LessHighlighter
39 ignoredFunctionNames = set([
42 'glXGetCurrentDisplay',
44 'glXGetProcAddressARB',
49 class Loader(Unpickler):
51 def __init__(self, stream):
52 Unpickler.__init__(self, stream)
55 def handleCall(self, call):
56 if call.functionName not in ignoredFunctionNames:
57 self.calls.append(call)
61 def readtrace(trace, calls):
70 stdout = subprocess.PIPE,
74 parser = Loader(p.stdout)
81 def __init__(self, a, b, highlighter, callNos = False):
84 self.highlighter = highlighter
85 self.delete_color = highlighter.red
86 self.insert_color = highlighter.green
87 self.callNos = callNos
90 self.dumper = Dumper()
93 matcher = difflib.SequenceMatcher(self.isjunk, self.a, self.b)
94 for tag, alo, ahi, blo, bhi in matcher.get_opcodes():
96 self.replace(alo, ahi, blo, bhi)
98 self.delete(alo, ahi, blo, bhi)
100 self.insert(alo, ahi, blo, bhi)
102 self.equal(alo, ahi, blo, bhi)
104 raise ValueError, 'unknown tag %s' % (tag,)
106 def isjunk(self, call):
107 return call.functionName == 'glGetError' and call.ret in ('GL_NO_ERROR', 0)
109 def replace(self, alo, ahi, blo, bhi):
110 assert alo < ahi and blo < bhi
112 a_names = [call.functionName for call in self.a[alo:ahi]]
113 b_names = [call.functionName for call in self.b[blo:bhi]]
115 matcher = difflib.SequenceMatcher(None, a_names, b_names)
116 for tag, _alo, _ahi, _blo, _bhi in matcher.get_opcodes():
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)
128 self.replace_similar(_alo, _ahi, _blo, _bhi)
130 raise ValueError, 'unknown tag %s' % (tag,)
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('(')
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])
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')
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)
163 first = self.delete(alo, ahi)
164 second = self.insert(blo, bhi)
166 for g in first, second:
170 def replace_value(self, a, b):
172 self.highlighter.write(self.dumper.visit(b))
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()
185 def delete(self, alo, ahi, blo, bhi):
186 for i in xrange(alo, ahi):
188 self.highlighter.write('- ')
189 self.dumpCallNos(call.no, None)
190 self.highlighter.strike()
191 self.highlighter.color(self.delete_color)
194 def insert(self, alo, ahi, blo, bhi):
195 for i in xrange(blo, bhi):
197 self.highlighter.write('+ ')
198 self.dumpCallNos(None, call.no)
199 self.highlighter.color(self.insert_color)
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)
212 def dumpCallNos(self, aNo, bNo):
217 self.highlighter.write(' '*self.aSpace)
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(' ')
227 self.highlighter.write(' '*self.aSpace)
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(' ')
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')
251 # Parse command line options
252 optparser = optparse.OptionParser(
253 usage='\n\t%prog <trace> <trace>',
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(
274 dest="call_nos", default=False,
275 help="dump call numbers")
277 (options, args) = optparser.parse_args(sys.argv[1:])
279 optparser.error("incorrect number of arguments")
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
286 ref_calls = readtrace(args[0], options.ref_calls)
287 src_calls = readtrace(args[1], options.src_calls)
289 if sys.stdout.isatty():
290 highlighter = LessHighlighter()
292 highlighter = ColorHighlighter()
294 differ = SDiffer(ref_calls, src_calls, highlighter, options.call_nos)
298 if __name__ == '__main__':