]> git.cworth.org Git - apitrace/blob - scripts/tracediff2.py
Improve profiling.
[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, Rebuilder
36 from highlight import ColorHighlighter, LessHighlighter
37
38
39 ignoredFunctionNames = set([
40     'glGetString',
41     'glXGetClientString',
42     'glXGetCurrentDisplay',
43     'glXGetCurrentContext',
44     'glXGetProcAddress',
45     'glXGetProcAddressARB',
46     'wglGetProcAddress',
47 ])
48
49
50 class Blob:
51     '''Data-less proxy for bytearrays, to save memory.'''
52
53     def __init__(self, size, hash):
54         self.size = size
55         self.hash = hash
56
57     def __repr__(self):
58         return 'blob(%u)' % self.size
59
60     def __eq__(self, other):
61         return self.size == other.size and self.hash == other.hash
62
63     def __hash__(self):
64         return self.hash
65
66
67 class BlobReplacer(Rebuilder):
68     '''Replace blobs with proxys.'''
69
70     def visitByteArray(self, obj):
71         return Blob(len(obj), hash(str(obj)))
72
73     def visitCall(self, call):
74         call.args = map(self.visit, call.args)
75         call.ret = self.visit(call.ret)
76
77
78 class Loader(Unpickler):
79
80     def __init__(self, stream):
81         Unpickler.__init__(self, stream)
82         self.calls = []
83         self.rebuilder = BlobReplacer()
84
85     def handleCall(self, call):
86         if call.functionName not in ignoredFunctionNames:
87             self.rebuilder.visitCall(call)
88             self.calls.append(call)
89
90
91 def readtrace(trace, calls):
92     p = subprocess.Popen(
93         args = [
94             options.apitrace,
95             'pickle',
96             '--symbolic',
97             '--calls=' + calls,
98             trace
99         ],
100         stdout = subprocess.PIPE,
101     )
102
103     parser = Loader(p.stdout)
104     parser.parse()
105     return parser.calls
106
107
108 class SDiffer:
109
110     def __init__(self, a, b, highlighter, callNos = False):
111         self.a = a
112         self.b = b
113         self.highlighter = highlighter
114         self.delete_color = highlighter.red
115         self.insert_color = highlighter.green
116         self.callNos = callNos
117         self.aSpace = 0
118         self.bSpace = 0
119         self.dumper = Dumper()
120
121     def diff(self):
122         matcher = difflib.SequenceMatcher(self.isjunk, self.a, self.b)
123         for tag, alo, ahi, blo, bhi in matcher.get_opcodes():
124             if tag == 'replace':
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)
130             elif tag == 'equal':
131                 self.equal(alo, ahi, blo, bhi)
132             else:
133                 raise ValueError, 'unknown tag %s' % (tag,)
134
135     def isjunk(self, call):
136         return call.functionName == 'glGetError' and call.ret in ('GL_NO_ERROR', 0)
137
138     def replace(self, alo, ahi, blo, bhi):
139         assert alo < ahi and blo < bhi
140         
141         a_names = [call.functionName for call in self.a[alo:ahi]]
142         b_names = [call.functionName for call in self.b[blo:bhi]]
143
144         matcher = difflib.SequenceMatcher(None, a_names, b_names)
145         for tag, _alo, _ahi, _blo, _bhi in matcher.get_opcodes():
146             _alo += alo
147             _ahi += alo
148             _blo += blo
149             _bhi += blo
150             if tag == 'replace':
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)
156             elif tag == 'equal':
157                 self.replace_similar(_alo, _ahi, _blo, _bhi)
158             else:
159                 raise ValueError, 'unknown tag %s' % (tag,)
160
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('(')
174             sep = ''
175             numArgs = max(len(a_call.args), len(b_call.args))
176             for j in xrange(numArgs):
177                 self.highlighter.write(sep)
178                 try:
179                     a_arg = a_call.args[j]
180                 except IndexError:
181                     pass
182                 try:
183                     b_arg = b_call.args[j]
184                 except IndexError:
185                     pass
186                 self.replace_value(a_arg, b_arg)
187                 sep = ', '
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')
193
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)
199         else:
200             self.delete(alo, ahi, blo, blo)
201             self.insert(ahi, ahi, blo, bhi)
202
203     def replace_value(self, a, b):
204         if b == a:
205             self.highlighter.write(self.dumper.visit(b))
206         else:
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()
215
216     escape = "\33["
217
218     def delete(self, alo, ahi, blo, bhi):
219         assert alo < ahi
220         assert blo == bhi
221         for i in xrange(alo, ahi):
222             call = self.a[i]
223             self.highlighter.write('- ')
224             self.dumpCallNos(call.no, None)
225             self.highlighter.strike()
226             self.highlighter.color(self.delete_color)
227             self.dumpCall(call)
228
229     def insert(self, alo, ahi, blo, bhi):
230         assert alo == ahi
231         assert blo < bhi
232         for i in xrange(blo, bhi):
233             call = self.b[i]
234             self.highlighter.write('+ ')
235             self.dumpCallNos(None, call.no)
236             self.highlighter.color(self.insert_color)
237             self.dumpCall(call)
238
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)
250
251     def dumpCallNos(self, aNo, bNo):
252         if not self.callNos:
253             return
254
255         if aNo is None:
256             self.highlighter.write(' '*self.aSpace)
257         else:
258             aNoStr = str(aNo)
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(' ')
265         if bNo is None:
266             self.highlighter.write(' '*self.bSpace)
267         else:
268             bNoStr = str(bNo)
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(' ')
274
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')
284
285
286 def main():
287     '''Main program.
288     '''
289
290     # Parse command line options
291     optparser = optparse.OptionParser(
292         usage='\n\t%prog <trace> <trace>',
293         version='%%prog')
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(
311         '--call-nos',
312         action="store_true",
313         dest="call_nos", default=False,
314         help="dump call numbers")
315     global options
316     (options, args) = optparser.parse_args(sys.argv[1:])
317     if len(args) != 2:
318         optparser.error("incorrect number of arguments")
319
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
324
325     ref_calls = readtrace(args[0], options.ref_calls)
326     src_calls = readtrace(args[1], options.src_calls)
327
328     if sys.stdout.isatty():
329         highlighter = LessHighlighter()
330     else:
331         highlighter = ColorHighlighter()
332
333     differ = SDiffer(ref_calls, src_calls, highlighter, options.call_nos)
334     try:
335         differ.diff()
336     except IOError:
337         pass
338
339
340 if __name__ == '__main__':
341     main()