blob: 1115bfb1878898a8b2270387da7f11ae11ae9e8e [file] [log] [blame]
Jose Fonseca731ccce2016-01-19 12:36:27 +00001#!/usr/bin/env python
2##########################################################################
3#
4# Copyright 2014-2016 VMware, Inc.
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
28import subprocess
29import sys
30import os.path
31import optparse
Jose Fonseca771c34f2016-01-19 14:41:55 +000032import re
Jose Fonseca731ccce2016-01-19 12:36:27 +000033
34import unpickle
35
36
37class ContextState:
38
39 def __init__(self):
Jose Fonseca771c34f2016-01-19 14:41:55 +000040 # a map of maps
41 self.objectDicts = {}
Jose Fonseca731ccce2016-01-19 12:36:27 +000042
43
44class LeakDetector(unpickle.Unpickler):
45
46 def __init__(self, apitrace, trace):
47
48 cmd = [apitrace, 'pickle', '--symbolic', trace]
49 p = subprocess.Popen(args = cmd, stdout = subprocess.PIPE)
50
51 unpickle.Unpickler.__init__(self, p.stdout)
52
53 self.context = ContextState()
54
55 def parse(self):
56 unpickle.Unpickler.parse(self)
57
58 # Reached the end of the trace -- dump any live objects
59 self.dumpLeaks("<EOF>", self.context)
60
Jose Fonseca771c34f2016-01-19 14:41:55 +000061 genDelRegExp = re.compile('^gl(Gen|Delete)(Buffers|Textures|FrameBuffers|RenderBuffers)[A-Z]*$')
62
Jose Fonseca731ccce2016-01-19 12:36:27 +000063 def handleCall(self, call):
64 # Ignore calls without side effects
65 if call.flags & unpickle.CALL_FLAG_NO_SIDE_EFFECTS:
66 return
67
68 # Dump call for debugging:
Jose Fonseca771c34f2016-01-19 14:41:55 +000069 if 0:
70 sys.stderr.write('%s\n' % call)
Jose Fonseca731ccce2016-01-19 12:36:27 +000071
72 # FIXME: keep track of current context on each thread (*MakeCurrent)
73 context = self.context
74
Jose Fonseca771c34f2016-01-19 14:41:55 +000075 mo = self.genDelRegExp.match(call.functionName)
76 if mo:
77 verb = mo.group(1)
78 subject = mo.group(2)
Jose Fonseca731ccce2016-01-19 12:36:27 +000079
Jose Fonseca771c34f2016-01-19 14:41:55 +000080 subject = subject.lower().rstrip('s')
81 objectDict = context.objectDicts.setdefault(subject, {})
82
83 if verb == 'Gen':
84 self.handleGenerate(call, objectDict)
85 elif verb == 'Delete':
86 self.handleDelete(call, objectDict)
87 else:
88 assert 0
Jose Fonseca731ccce2016-01-19 12:36:27 +000089
90 if call.functionName in [
91 'glXDestroyContext',
92 'eglDestroyContext',
93 'wglDeleteContext',
94 ]:
95 self.dumpLeaks(call.no, context)
96
Jose Fonseca771c34f2016-01-19 14:41:55 +000097 def handleGenerate(self, call, objectDict):
98 n, names = call.argValues()
99 for i in range(n):
100 name = names[i]
101 objectDict[name] = call.no
102
103 def handleDelete(self, call, objectDict):
104 n, names = call.argValues()
105 for i in range(n):
106 name = names[i]
107 try:
108 del objectDict[name]
109 except KeyError:
110 # Ignore if texture name was never generated
111 pass
112
113 def dumpLeaks(self, currentCallNo, context):
114 for kind, objectDict in context.objectDicts.iteritems():
115 self.dumpNamespaceLeaks(currentCallNo, objectDict, kind)
116
117 def dumpNamespaceLeaks(self, currentCallNo, objectDict, kind):
118 for name, creationCallNo in objectDict.iteritems():
119 sys.stderr.write('%u: error: %s %u was not destroyed until %s\n' % (creationCallNo, kind, name, currentCallNo))
120 objectDict.clear()
Jose Fonseca731ccce2016-01-19 12:36:27 +0000121
122
123def main():
124 '''Main program.
125 '''
126
127 # Parse command line options
128 optparser = optparse.OptionParser(
129 usage='\n\t%prog [options] TRACE',
130 version='%%prog')
131 optparser.add_option(
132 '-a', '--apitrace', metavar='PROGRAM',
133 type='string', dest='apitrace', default='apitrace',
134 help='apitrace command [default: %default]')
135
136 options, args = optparser.parse_args(sys.argv[1:])
137 if len(args) != 1:
138 optparser.error("incorrect number of arguments")
139
140 inTrace = args[0]
141 if not os.path.isfile(inTrace):
142 sys.stderr.write("error: `%s` does not exist\n" % inTrace)
143 sys.exit(1)
144
145 detector = LeakDetector(options.apitrace, inTrace)
146 detector.parse()
147
148
149if __name__ == '__main__':
150 main()